- O(n^2)复杂度算法
- 冒泡排序
- 插入排序
- 选择排序
- O(nlogn)复杂度算法
- 归并排序
- 快速排序
- 堆排序
- 总结
排序是在生活中经常遇到的事情,比如在学校时会按成绩排名,会按身高排座位。在逛电子商铺时,会按销售量/人气值筛选,刷微博时,会有热搜榜等。将很多无序的事情变的有序,事情就好处理了。其中用到的就是排序算法。
拿班主任给班级学生成绩排序来举例。 O(n^2)复杂度算法 冒泡排序
第一轮从成绩单中挑出来成绩最高的,第二轮从成绩单中挑出来成绩次高的。。。这么重复下去,直到班级内全部学生全部挑出来完。
因为每一次选出一个最好的,就像从水里冒出的气泡,所以叫冒泡排序。
void BubbleSort(vector插入排序&sums) { // 第i轮次挑学生 for (int i = 0; i < nums.size() - 1; i++) { // 成绩单中还没被挑出来的学生 for (int j = 0; j < nums.size() - 1 - i; j++) { if (nums[j] < nums[j+1]) { swap(nums[j],nums[j+1]); } } } }
先把成绩单中第一个学生的成绩写到一张白纸的中间,如果第二个学生成绩比他高则写到第一位同学的上方,如果第二个学生成绩比他低,则写到第一个同学下方。等到了第三个同学的时候,拿第三名同学的成绩和白纸上的两个同学成绩比较,插入到相应的位置。排序的纸要留好空位,以方便插入后来的同学的名字。
这个排序算法就像平时大扑克摸牌一样,后摸的牌和手里的牌比较后,插入到手中合适的位置,所以叫插入排序。
void InsertSort(vector选择排序&nums) { // 要插入到白纸上的同学的序号 for (int i = 1; i < nums.size(); i++) { int tmp = nums[i]; // j是白纸上的同学的末尾序号 int j = i - 1; // 循环找出插入的位置 while (j >=0 && nums[j] < tmp) { nums[j+1] = nums[j]; j--; } nums[j+1] = tmp; } }
从1轮从成绩单中选出成绩最高的同学,写到第一个位置,再选择次高的成绩,写到第二个位置。。。以此类推,很像像冒泡排序,不同的是:先假设成绩单中无序区的第一个学生成绩为最好,拿排好序的学生成绩最好的那个和这个成绩相比,并决定是否交换。
void SelectSort(vectorO(nlogn)复杂度算法&nums) { for (int i = 0; i < nums.size() - 1; i++) { // 记录位置 int max_loc = i; for (int j = i + 1; j < nums.size(); j++) { // 成绩单中无序区 if (nums[j] > nums[max_loc]) { max_loc = j; } } swap(nums[max_loc, nums[i]]); } }
上面3种排序都是复杂度为O(n^2)复杂度的算法,当数据量小的情况下,排序不怎么费时间。但是当数据量大的情况下,上面三种算法就显的太慢了。要想提高效率,就要让计算机少做事情(减少数据之间的相互比较)。
归并排序把全班同学分成两组,分别排序,那么从每一组中挑选出一个最大的,就能省去一般的相互比较时间。那么:
1.就把整个班级一分为二,分别进行排序,再把两个有序数列合并成一个排好序的序列。相比排序,对有序数列的合并是很快的。
2.对班级分成2个组的排序,再分成4组,分别排序,那么就节省了3/4的时间。
3.4组分8组,8组分16组,直到每组分为2个元素,做一次对比就行。
4.合并有序数组,这样列表越来越大,知道成为一个班级中所有的人。
void Merge(vector快速排序&nums, int low, int mid, int high) { int i = low; int j = mid + 1; vector ltmp; // 只要左右两个有序序列都有数,开始循环比较 while (i <= mid && j <= high) { if (nums[i] < nums[j]) { ltmp.push_back(nums[i]); i++; } else { ltmp.push_back(nums[j]); j++; } } // 循环结束,两个序列肯定有一个先没有数据 while (i <= mid) { ltmp.push_back(nums[i]); i++; } while (j <= high) { ltmp.push_back(nums[j]); j++; } for (int k = 0; k < high - low + 1; k++) { nums[low + k] = ltmp[k]; } } void MergeSort(vector &nums, int low, int high) { // 至少有两个元素 if (low < high) { int mid = (low + high) / 2; // 让左右数列有序 MergeSort(nums, low, mid); MergeSort(nums, mid + 1, high); // 合并 Merge(nums, low, mid, high); } }
从班级学生成绩中随机选出来一个,比如我们选的是序号为0的学生,他的是70分。
1.归类:将班级所有学生成绩分成两部分,一部分是大于70分的,令一部分小于70分。
2.从上面得到的两部分学生中,分别采用第一步的方法再选出来一个学生,我们还假设选了两波人中最头部的那个学生,比如分别是50分和80分,并再分拨。
3.用同样的方法,四拨人变八拨。。。变十六拨,这样下去,所有的数字就会排序好。
int Partition(vector堆排序&nums, int left, int right) { int tmp = nums[left]; while (left < right) { // 从右边找比tmp大的数,找到后把大的数放到左边 while (nums[right] <= tmp && left < right) { right--; } nums[left] = nums[right]; // 从左边找比tmp小的数,找到后放到右边 while (nums[left] >= tmp && left < right) { left++; } nums[right] = nums[left]; } // 把tmp归位,此时left = right nums[left] = tmp; return left; } void QuickSort(vector &nums, int left, int right) { if (left < right) { int mid = Partition(nums, left, right); QuickSort(nums, left, mid - 1); QuickSort(nums, mid + 1, right); } }
这个涉及到树的知识,笔者就不深入写了。总的过程可以分为一下几部
1.建立堆
2.得到堆顶元素,为最大元素
3.去掉堆顶,将堆最后一个元素放到堆顶,此时可以通过一次调整,让堆重新变得有序。
4.堆顶元素为第二大元素。
5.重复步骤3,直到堆变空。
// 堆的向下调整 void Sift(vector总结&nums, int low, int high) { // low:堆的根节点位置 // high:堆的最后一个元素的位置 // i是父节点,j是左孩子结点 int i = low; int j = 2 * i + 1; int tmp = nums[low]; while (j <= high) { // 如果有右孩子,且右孩子比左孩子小 if (j + 1 <= high && nums[j+1] < nums[j]) { // j指向右孩子 j++; } if (nums[j] < tmp) { nums[i] = nums[j]; // 向下看一层 i = j; j = 2 * i + 1; } // 如果tmp更大,把tmp放在这个根节点位置 else { nums[i] = tmp; break; } } nums[i] = tmp; } void HeapSort(vector &nums) { int n = nums.size(); // 建立堆 for (int i = (n - 1 - 1) / 2; i >=0; i++) { // 建立堆的时候,调整的部分的根的下标 Sift(nums, i , n - 1); } for (int i = n - 1; i >= 0; i--) { // i指向当前堆的最后一个元素 swap(nums[0], nums[i]); sift(nums, 0, i - 1); } }
| 排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|
| 冒泡 | O(n^2) | O(1) | 稳定 |
| 选择 | O(n^2) | O(1) | 不稳定 |
| 插入 | O(n^2) | O(1) | 稳定 |
| 快速 | O(nlogn) | O(logn),O(n) | 不稳定 |
| 归并 | O(nlogn) | O(n) | 稳定 |
| 堆 | O(nlogn) | O(1) | 不稳定 |
从算法的对比我们可以看出,很容易因为算法不合格,写出的代码浪费掉成千上万的计算机资源。我们平时做事,人和人的水平差一些,很可能做出来事的效率差别十倍。所以我们在智能时代,要提高自己的专业能力,并且要善于把大目标分解成小目标,不断拆解目标任务。



