常用排序算法及其实现
一、常用排序算法及滑稽实现
1. 插入排序:遍历数组(n),将每个元素插入到前面子序列的合适位置(插入时采取前面的部分元素后移,再将本元素填在适当位置的方法)
- 平均:O(n2)
- 最坏:O(n2)
- 最好:O(n)(有序时出现)
- 稳定性:稳定(相同元素在排序之后相对位置不会改变)
模拟:
12 30 9 100 1 3 10
12 30 9 100 1 3 10
9 12 30 100 1 3 10
9 12 30 100 1 3 10
1 9 12 30 100 3 10
1 3 9 12 30 100 10
1 3 9 10 12 30 100
实现代码:
1 /* 2 插入排序: 3 平均:O(n2) 4 最好:O(n) 5 最坏:O(n2) 6 稳定性:稳定 7 */ 8 void insertSort(vector<int> &nums) 9 { 10 int len = nums.size(), i, j; 11 if(len <= 0) 12 return ; 13 for(i=1; i<len; i++) 14 { 15 for(j=0; j<i; j++) 16 { 17 if(nums[i] >= nums[j] && nums[i] < nums[j+1]) 18 { 19 int tmp = nums[i]; 20 for(int t=i; t>j; t--) 21 nums[t] = nums[t-1]; 22 nums[j] = tmp; 23 break; 24 } 25 } 26 } 27 }
2. 冒泡排序:双层循环,外层从后往前,里层从前往后,如果后面比前面小,就交换。
- 平均:O(n2)
- 最坏:O(n2)
- 最好:O(n) 优化后有序时出现
- 稳定性:稳定
模拟:
1 3 2 7 5 6 9 8 4
1 2 3 5 6 7 8 4 9
1 2 3 5 6 7 4 8 9
1 2 3 5 6 4 7 8 9
1 2 3 5 4 6 7 8 9
1 2 3 4 5 6 7 8 9
实现代码:
1 /* 2 冒泡排序: 通过两两交换的方式,每次将子区间最大的冒向最后 3 平均:O(n2) 4 最好:O(n) 5 最坏:O(n2) 6 稳定性:稳定 7 */ 8 //普通冒泡排序 9 void bubbleSort(vector<int> &nums) 10 { 11 int len = nums.size(); 12 if(len <= 0) 13 return ; 14 for(int i=len-1; i>=1; i--) 15 { 16 for(int j=1; j<=i; j++) 17 { 18 if(nums[j] < nums[j-1]) 19 { 20 int tmp = nums[j]; 21 nums[j] = nums[j-1]; 22 nums[j-1] = tmp; 23 } 24 } 25 } 26 } 27 28 //冒泡优化版本:添加标志位或者计数器判断序列是否已经有序 29 void bubbleSort2(vector<int> &nums) 30 { 31 int len = nums.size(); 32 if(len <= 0) 33 return ; 34 for(int i=len-1; i>=1; i--) 35 { 36 int flag = 0;//或者使用bool值,更好一些 37 for(int j=1; j<=i; j++) 38 { 39 if(nums[j] < nums[j-1]) 40 { 41 int tmp = nums[j]; 42 nums[j] = nums[j-1]; 43 nums[j-1] = tmp; 44 } 45 else 46 flag ++; 47 } 48 if(flag == i) 49 return ; 50 } 51 }
3. (简单)选择排序:本位和后面子序列中最小的交换
- 平均:O(n2)
- 最坏:O(n2)
- 最好:O(n2)
- 稳定性:不稳定
模拟:
1 3 2 7 5 6 9 8 4
1 3 2 7 5 6 9 8 4
1 2 3 7 5 6 9 8 4
1 2 3 7 5 6 9 8 4
1 2 3 4 5 6 9 8 7
1 2 3 4 5 6 9 8 7
1 2 3 4 5 6 9 8 7
1 2 3 4 5 6 7 8 9
实现代码:
1 /* 2 选择排序: 每个子区间找到最小与第一个交换 3 平均:O(n2) 4 最好:O(n) 5 最坏:O(n2) 6 稳定性:稳定 7 */ 8 void chooseSort(vector<int> &nums) 9 { 10 int len = nums.size(); 11 if (len <= 0) 12 return; 13 for (int i = 0; i < len - 1; i++) 14 { 15 int min = i + 1; 16 for (int j = i + 1; j < len; j++) 17 { 18 if (nums[j] < nums[min]) 19 min = j; 20 } 21 if (nums[i] > nums[min]) 22 swap(nums[i], nums[min]); 23 } 24 }
4. 快排:思想是,一趟挑出1个数,将数组分成左右两部分,左边小于该数,右边大于该数。维护有两个索引(low, high),随机将数组中一个数定为“基准”,先从high右往左找到第一个比基准小的与基准交换,更新low和high,再从low左往右找第一个大于基准的与基准交换,更新low和high。直到low == high。进行下一趟,一般三趟足以。(第一堂完成后左边再选一个,右边再选一个即可。)
- 平均:O(n*log2n)
- 最好:O(n*log2n)
- 最坏:O(n2) (当有序时出现,即每次都是划分成为1,n-1的子序列)
- 稳定性:不稳定
模拟:
1 3 2 7 5 6 9 8 4
4 3 2 7 5 6 9 8 1
4 3 2 1 5 6 9 8 7
(一趟结束)
实现代码:
1 /* 2 快速排序: 3 平均:O(nlogn) 4 最好:O(nlogn) 5 最坏:O(n2) 完全有序时出现 6 稳定性:不稳定 7 */ 8 //分治法:递归实现 9 void quickSort(vector<int> &nums, int l, int r) 10 { 11 if(l >= r) 12 return ; 13 int i = l, j = r+1; 14 int key = nums[l]; 15 //按照key的大小分割两块,供后续递归排序 16 while(true) 17 { 18 //从左往右找到第一个大于key的数 19 while(nums[++i] < key) 20 { 21 if(i == r) 22 break; 23 } 24 //从右往左找到第一个小于key的数 25 while(nums[--j] > key) 26 { 27 if(j == l) 28 break; 29 } 30 //判断位置,交换两数 31 if(i >= j) 32 break; 33 int tmp = nums[i]; 34 nums[i] = nums[j]; 35 nums[j] = tmp; 36 } 37 //分割完成后,递归两部分 38 int tmp = nums[l]; 39 nums[l] = nums[j]; 40 nums[j] = tmp; 41 quickSort(nums, l, j-1); 42 quickSort(nums, j+1, r); 43 }
5. 堆排序:对所给数组进行建大顶堆,将堆顶元素与最后一个交换,尾索引前移,继续建立大顶堆,如此类推。
- 平均:O(n*log2n)
- 最坏:O(n*log2n)
- 稳定性:不稳定
1 /* 2 堆排序: 在数组基础上构建大/小顶堆来实现排序 3 平均:O(nlogn) 4 最好:O(nlogn) 5 最坏:O(nlogn) 6 稳定性:不稳定 7 */ 8 //调整范围堆顶值,保持大顶 9 void heapAdjust(vector<int> &nums, int index, int length) 10 { 11 int max = index; 12 int left = index * 2 + 1; //左子节点 13 int right = index * 2 + 2; //右子节点 14 if (left < length && nums[left] > nums[max]) 15 max = left; 16 if (right < length && nums[right] > nums[max]) 17 max = right; 18 if (max != index) 19 { 20 int tmp = nums[index]; 21 nums[index] = nums[max]; 22 nums[max] = tmp; 23 heapAdjust(nums, max, length); 24 } 25 return; 26 } 27 28 //堆排序:递归方式实现 29 void heapSort(vector<int> &nums) 30 { 31 int len = nums.size(); 32 if (len <= 1) 33 return; 34 //调整成大顶堆,数组第一个元素值就是最大值 35 for (int i = len / 2 - 1; i >= 0; i--) 36 { 37 heapAdjust(nums, i, len); 38 } 39 //堆排序 40 for (int i = len - 1; i >= 0; i--) 41 { 42 int tmp = nums[0]; 43 nums[0] = nums[i]; 44 nums[i] = tmp; 45 printNums(nums); 46 //继续调整 47 heapAdjust(nums, 0, i); 48 } 49 }
6.归并排序:将原始数组分成若干个子序列,两两合并排序,再合并排序......直到合并完成。
- 平均:O(n*log2n)
- 最坏:O(n*log2n)
(同数量级排序方法中性能最好的---又快又稳定)
模拟:
1 3 2 7 5 6 9 8 4
[1 3] [2 7] [5 6] [8 9] [4]
[1 2 3 7] [5 6 8 9] [4]
[1 2 3 5 6 8 9] [4]
[1 2 3 4 5 6 7 8 9]
实现代码:
1 /* 2 二路归并排序: 3 平均:O(nlogn) 4 最好:O(nlogn) 5 最坏:O(nlogn) 完全有序时出现 6 稳定性:稳定,但很少用 7 */ 8 //合并二路数组 9 void merge(vector<int> &nums, vector<int> &res, int l, int m, int r) 10 { 11 int i = l;//前半部分起点 12 int res_idx = l;//合并数组起点 13 int j = m+1;//后半部分起点 14 //谁小取谁 15 while(i <= m && j <= r) 16 { 17 if(nums[i] <= nums[j]) 18 res[res_idx++] = nums[i++]; 19 else 20 res[res_idx++] = nums[j++]; 21 } 22 //剩下的部分 23 if(i <= m) 24 { 25 while(i <= m) 26 res[res_idx++] = nums[i++]; 27 } 28 else 29 { 30 while(j <= r) 31 res[res_idx++] = nums[j++]; 32 } 33 //将二路合并结果复制回去 34 for(int i=l; i<=r; i++) 35 nums[i] = res[i]; 36 } 37 38 //二路分割 39 void mergeSort(vector<int> &nums, vector<int> &res, int l, int r) 40 { 41 if(l < r) 42 { 43 int m = l + (r-l)/2; 44 mergeSort(nums, res, l, m); 45 mergeSort(nums, res, m+1, r); 46 merge(nums, res, l, m, r); 47 } 48 }
7. 希尔排序:要选步长,每隔一个步长选定一个元素,对选定的序列排序,再选一次再排;完成后换步长再排。步长的选取很重要,但没有好的选取方法。