算法_八大排序算法总结
最近笔试面试中经常考到排序算法,及其对应的时间复杂度和空间复杂度分析,现做如下总结。
一,冒泡排序
思想:对于0~n-1,依次比较相邻两个数,前者比后者大就交换,一轮后A[n-1]是最大数,在对0~n-2执行以上步骤,则A[n-2]是第二大的数,循环执行上面的步骤即可,形象的可以理解为大的数一个个冒到后面去,所以叫冒泡排序。
示意图:
首先6和3比较,6比3大,交换
6和5比较,交换
6和7比较,不用交换
依次执行以上步骤,第一轮后,序列变为
接着,在0到n-2上执行以上步骤
一轮过后,则变为
依次执行以上步骤,最后序列为
时间复杂度: O(n^2)
空间复杂度:O(1)
代码:
1 class BubbleSort { 2 public: 3 int* bubbleSort(int* A, int n) 4 { 5 int temp; 6 // write code here 7 for(int i = 0; i < n; i++) 8 { 9 for(int j = 0; j < n - i - 1; j++) 10 { 11 if(A[j] > A[j+1]) 12 { 13 temp = A[j]; 14 A[j] = A[j + 1]; 15 A[j + 1] = temp; 16 } 17 } 18 19 } 20 return A; 21 } 22 };
二,选择排序
思想:在序列中依次选择最小值放到最前端,重复以上步骤,只到排序完成
示意图:
最小数为0,放到最前端
1到n-1最小数为1,放到最前端
依次执行以上步骤,最后为
时间复杂度:O(n^2)
空间复杂度:O(1)
代码:
1 class SelectionSort { 2 public: 3 int* selectionSort(int* A, int n) 4 { 5 // write code here 6 //从前往后依次放入为排序的数组的最小值 7 int min_b; 8 int temp; 9 for(int i = 0; i < n - 1; i++) 10 { 11 min_b = i; 12 for(int j = i; j < n; j++) //寻找最小值 13 { 14 if(A[min_b] > A[j]) 15 min_b = j; 16 17 } 18 temp = A[i]; 19 A[i] = A[min_b]; 20 A[min_b] = temp; 21 } 22 return A; 23 } 24 };
三,插入排序
思想:对于数组A[n],保证前面的A[0]~A[m]是排序好的,再把A[m+1]插入到前面排好序的序列中,m递增,知道m=n-2
示意图:
原始序列为:
6和5比较,6比5大,要交换
接下来把3插入到前面排好序的序列中,首先3和6比,6大,后移一位
接着3和5比较,5大,后移一位
只到前面没有数了,或者前面的数比要插入的数小,就在对应的位置插入该数
再对1执行以上步骤
重复以上步骤,只到整个序列排序完成
时间复杂度:O(n^2)
空间复杂度:O(1)
代码
1 class InsertionSort { 2 public: 3 int* insertionSort(int* A, int n) 4 { 5 // write code here 6 int temp; 7 for(int i = 1; i < n; i ++) 8 { 9 temp = A[i]; 10 for(int j = i - 1; j >= 0; j--) 11 { 12 if(temp < A[j]) 13 { 14 A[j + 1] = A[j]; 15 if(j == 0) 16 { 17 A[j] = temp; 18 } 19 } 20 else 21 { 22 A[j + 1] = temp; 23 break; 24 } 25 } 26 } 27 return A; 28 } 29 };
四,归并排序
思想:对数组中每个数看成是长度为1的有序区间,接着合并相邻两个长度为1的有序区间,变为长度为2的有序区间,接着合并相邻长度为2的有序区间变成长度为4的有序区间,依次进行,只到排序完成
示意图:
首先为长度为1的有序区间
合并为长度为2的有序区间
合并为长度为4的有序区间
合并为长度为8的有序区间,排序完成
时间复杂度:O(nlogn)
空间复杂度:O(N)
代码
1 class MergeSort { 2 public: 3 int* mergeSort(int* A, int n) 4 { 5 mergeSort(A,0,n-1); 6 return A; 7 8 } 9 void mergeSort(int* A, int left, int right) 10 { 11 if(left == right) 12 return; 13 int mid=(left+right)/2; 14 mergeSort(A,left,mid); 15 mergeSort(A,mid+1,right); 16 merge_p(A,left,mid,right); 17 return; 18 } 19 20 void merge_p(int* A, int left, int mid, int right) 21 { 22 int* temp = new int[right - left + 1]; 23 int l = left; 24 int r = mid + 1; 25 int k = 0; 26 while(l <= mid && r <= right) 27 { 28 if(A[l] < A[r]) 29 temp[k++] = A[l++]; 30 else 31 temp[k++] = A[r++]; 32 } 33 while(l <= mid) 34 temp[k++] = A[l++]; 35 while(r <= right) 36 temp[k++] = A[r++]; 37 for(int i = 0; i < k; i++) 38 { 39 A[left + i] = temp[i]; 40 } 41 } 42 43 };
五,快速排序
思想:随机选择数组中的数,小于等于这个数的放在左边,大于这个数的放在右边,递归调用以上步骤,完成排序
示意图:
首先随机选择,划分区间
递归调用,即可完成排序。
问题的关键在于如何划分区间,即小于等于的数如何放在左边,大于的数如何放在右边,即Partition过程
首先假设选择3为枢纽源
把枢纽源和最后一个数字交换
维持一个小于等于区间
接下来从左到右遍历所有数,大于枢纽源的数则不动,小于等于枢纽源的数时把该数和小于等于区间的下一个数交换,并令小于等于区间右移一位,如此进行,只到最后一个数,即枢纽源,再把枢纽源和小于等于区间的下一个数交换即可。
接上图,4,5,6都大于3不用动,0小于3,要与小于等于区间的下一个数即4交换位置,小于等于区间右移一位,即:
如此进行下去
最后一步,把枢纽源3和小于等于区间的下一个数4交换位置
划分完毕,划分时间复杂度为O(n)。
时间复杂度: O(nlogn)
空间复杂度:O(logn) ~O(n)
代码
1 class QuickSort { 2 public: 3 4 void swap(int &a, int &b) 5 { 6 int c; 7 c = a; 8 a = b; 9 b = c; 10 } 11 12 void quick_s(int* A, int left, int right) //枢纽元直接取中间值 13 { 14 if(left >= right ) 15 return; 16 int mid = (left + right)/2; 17 int key = left; 18 swap(A[mid],A[right]); 19 for(int i = left; i < right; i++) 20 { 21 if(A[i] < A[right]) 22 { 23 swap(A[key],A[i]); 24 key++; 25 } 26 } 27 swap(A[key],A[right]); 28 quick_s(A,left,key - 1); 29 quick_s(A,key + 1,right); 30 } 31 32 33 34 int* quickSort(int* A, int n) 35 { 36 // write code here 37 quick_s( A, 0, n-1); 38 return A; 39 } 40 };
六,堆排序
思想: 把数组构建为一个大小为n的大根堆,堆顶为所有元素的最大值,把堆顶元素和最后一个值交换,作为有序部分放在最后,接着调整剩下的n-1个元素的大根堆,第二大的值就是堆顶元素,在和该堆的最后一个元素交换,并脱离堆作为有序部分,重复以上步骤。
示意图:
原始数组为
构建最大堆
堆顶元素和最后一个数交换,作为有序部分放到最后
调整最大堆
堆顶元素和最后一个数交换,作为有序部分放到最后
调整最大堆
重复以上步骤,直到排序完成
时间复杂度:O(nlogn)
空间复杂度:O(1)
代码
1 class HeapSort 2 { 3 public: 4 void Prcdown(int* A, int i, int n) 5 { 6 int tem; 7 int Child; 8 for(tem = A[i]; 2 * i + 1 < n; i = Child) 9 { 10 Child = 2 * i + 1; 11 if(Child != n-1 && A[Child + 1] > A[Child]) //寻找较大的儿子 12 Child++; 13 if(tem < A[Child]) 14 A[i] = A[Child]; 15 else 16 break; 17 } 18 A[i] = tem; 19 20 } 21 void Swap(int &a, int &b) 22 { 23 int c; 24 c = a; 25 a = b; 26 b = c; 27 } 28 int* heapSort(int* A, int n) 29 { 30 // write code here 31 int i; 32 for(i = n / 2; i >= 0; i--) //在数组上构建最大堆 33 Prcdown(A,i,n); 34 for(i = n - 1; i > 0; i--) 35 { 36 Swap(A[0],A[i]); //开始排序,把最大值换到后面去 37 Prcdown(A,0,i); //整理堆,剩下元素的最大值会回到开头 38 } 39 return A; 40 } 41 };
七,希尔排序
思想:希尔排序是插入排序的改良版,插入排序的步长为1,希尔排序的步长依次递减。
示意图:
假设原始数组为以下,步长为3
前三个数不考虑
从1开始比较,步长为3跳3步和6比较,6比1大,交换位置
1再往前跳3步就越界,因此开始比较8,跳3步和5比较,8比5大,不交换位置
停止8的交换,开始下一个数7 的交换,7比3大,不用交换
停止7的交换,开始2的交换,2和6比,2比6小,交换位置
2再跳3步和1比较,2比1大,不用交换,停止2的比较,开始4的交换,4和8比,4比8小,交换位置
4再跳3步和5比较,4比5小,交换位置
4再跳3步,越界,停止比较,步长为3调整结束,接下来步长为2和1调整,得到结果
时间复杂度:O(nlogn)
空间复杂度:O(1)
代码
1 class ShellSort 2 { 3 public: 4 int* shellSort(int* A, int n) 5 { 6 // write code here 7 int i, j; 8 int Increment = n/2; 9 int tmp; 10 while(Increment >= 1) 11 { 12 for(i = Increment; i < n; ++i) 13 { 14 tmp = A[i]; 15 for(j = i - Increment; j >= 0 && tmp < A[j]; j = j - Increment) 16 { 17 A[j + Increment] = A[j]; 18 } 19 A[j + Increment] = tmp; 20 } 21 Increment = Increment / 2; 22 } 23 return A; 24 } 25 };
八,通排序
桶排序不是基于比较的排序,因此时间复杂度可以达到O(n),空间复杂度为O(M),M为桶的个数,桶排序的思想在于是先把元素放入对应的桶中,再到出,结果即为排完序的结果。
基于桶排序思想的排序算法有基数排序和计数排序,原理差不多。
具体请看我的另外一篇博客https://www.cnblogs.com/1242118789lr/p/6858486.html
总结:
稳定的算法:冒泡排序,插入排序,归并排序,桶排序
不稳定的算法:选择排序,快速排序,希尔排序,堆排序
元素的移动次数与关键字的初始排列次序无关的是:基数排序
元素的比较次数与初始序列无关的是:选择排序
夜深了,,,
天亮了,昨晚平安夜。