1. 冒泡排序
冒泡排序从最下面的元素开始,对每两个相邻的关键字进行比较,且使关键字较小的元素换至关键字较大的元素之上,使得经过一趟冒泡排序后,关键字最小的元素到达最上端。
1 void BubbleSort(vector<int> &num) 2 { 3 int i, j, n; 4 5 n = num.size(); 6 for (i = 0; i < n - 1; i++) 7 { 8 for (j = n - 1; j > i; j--) 9 { 10 if (num[j] < num[j - 1]) 11 swap(num[j], num[j - 1]); 12 } 13 } 14 }
在有些情况下,在第i(i<n-1)趟时就已经排好序了,但算法仍执行后面几趟的比较。实际上,一旦算法中某一趟比较时没有出现过任何元素交换,就说明已经排好序了,就可以结束本算法。为此,改进冒泡排序算法如下:
1 void BubbleSort1(vector<int> &num) 2 { 3 int i, j, n; 4 bool exchange; 5 6 n = num.size(); 7 for (i = 0; i < n - 1; i++) 8 { 9 exchange = false; 10 for (j = n - 1; j > i; j--) 11 { 12 if (num[j] < num[j - 1]) 13 { 14 swap(num[j], num[j - 1]); 15 exchange = true; 16 } 17 } 18 19 if (!exchange) 20 return; 21 } 22 }
时间复杂度:
最好情况:O(n) 最坏情况:O(n2) 平均情况:O(n2)
空间复杂度:O(1)
稳定性:稳定
2. 快速排序
快速排序是由冒泡排序改进而得的,快速排序之所以快于冒泡排序,是因为它的每次交换是跳跃式的,不会像冒泡排序一样只能在相邻元素之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。
它的基本思想是:在待排序的n个元素中任取一个元素(通常取第一个元素)作为基准,把该元素放入适当位置后,数据序列被此元素划分成两部分,所有关键字比该元素关键字小的元素放置在前一部分,所有关键字比它大的元素放置在后一部分,并把该元素排在这两部分的中间(称该元素归位),这个过程称作一趟快速排序,之后对所有划分出来的两部分分别重复上述过程,直至每部分内只有一个元素或为空为止。
简言之,每趟将表的第一个元素放入适当位置,将表一分为二,对子表按递归方式继续这种划分,直至划分的子表长为1或0。C++实现如下:
1 void QuickSort(vector<int> &num, int s, int t) 2 { 3 int i = s, j = t; 4 5 if (s < t) 6 { 7 //注意要写在if语句里面,否则会出错,因为s有可能超出num的范围 8 int tmp = num[i]; 9 10 while (i < j) 11 { 12 //顺序很重要,要先从右边开始找 13 while (j > i && num[j] >= tmp) 14 j--; 15 num[i] = num[j]; 16 17 //再从左边开始找 18 while (i < j && num[i] <= tmp) 19 i++; 20 num[j] = num[i]; 21 } 22 23 num[i] = tmp; 24 25 QuickSort(num, s, i - 1); 26 QuickSort(num, i + 1, t); 27 } 28 }
时间复杂度:
最好情况:O(nlog2n) 最坏情况:O(n2) 平均情况:O(nlog2n)
空间复杂度:O(log2n)(因为递归树的高度为O(log2n))
稳定性:不稳定
3. 归并排序
归并排序是多次将两个或两个以上的有序表合并成一个新的有序表。最简单的归并是直接将两个有序的子表合并成一个有序的表即二路归并。二路归并排序的基本思路是:将R【0..n-1】看成是n个长度为1的有序序列,然后进行两两归并,得到floor(n/2)个长度为2(最后一个有序序列的长度可能为1)的有序序列,再进行两两归并,得到floor(n/4)个长度为4(最后一个有序序列的长度可能小于4)的有序序列......,直到得到一个长度为n的有序序列。C++实现如下:
void merge(vector<int> &num, int low, int mid, int high) { vector<int> tmp_merge; int i = low, j = mid + 1; //i、j分别为第1、2段的下标 while (i <= mid || j <= high) { if (i > mid) { tmp_merge.push_back(num[j]); j++; } else if (j > high) { tmp_merge.push_back(num[i]); i++; } else { if (num[i] < num[j]) { tmp_merge.push_back(num[i]); i++; } else { tmp_merge.push_back(num[j]); j++; } } } j = 0; for (i = low; i <= high; i++) { num[i] = tmp_merge[j]; j++; } } //对整个表进行一趟归并 void MergePass(vector<int> &num, int k) { int i, n; n = num.size(); for (i = 0; i + 2 * k <= n; i = i + 2 * k) //归并k长的两相邻子表 { merge(num, i, i + k - 1, i + 2 * k - 1); } if (i + k < n) //余下两个子表,后者长度小于length { merge(num, i, i + k - 1, n - 1); //归并这两个子表 } } //自底向上的二路归并算法 void MergeSort(vector<int> &num) { int n, k; n = num.size(); for (k = 1; k < n; k = 2 * k) //进行log n趟归并 { MergePass(num, k); } }
时间复杂度:
最好情况:O(nlog2n) 最坏情况:O(nlog2n) 平均情况:O(nlog2n)
空间复杂度:O(n)
稳定性:稳定
4. 直接插入排序
直接插入排序的一趟操作是将当前无序区的开头元素插入到有序区中适当的位置上,使之变成新的有序区。这种方法通常称为增量法,因为它每趟操作时有序区增加1个元素。C++实现为:
//直接插入排序 void InsertSort(vector<int> &num) { int i, j, n, tmp; n = num.size(); for (i = 1; i < n; i++) { tmp = num[i]; j = i - 1; while (j >= 0 && tmp < num[j]) //从右向左在有序区num[0..i-1]中找num[i]的插入位置 { num[j + 1] = num[j]; j--; } num[j + 1] = tmp; } }
时间复杂度:
最好情况:O(n) 最坏情况:O(n2) 平均情况:O(n2)
空间复杂度:O(1)
稳定性:稳定
5. 直接选择排序
直接选择排序的基本思想是:第i趟排序开始时,当前有序区和无序区分别为R[0..i-1]和R[i..n-1],该趟排序是从当前无序区中选出关键字最小的元素R[k],将它与无序区的第1个元素R[i]交换,使R[0..i]和R[i+1..n-1]分别变为新的有序区和新的无序区。每趟排序均使有序区中增加一个元素。C++实现如下:
1 //直接选择排序 2 void SelectSort(vector<int> &num) 3 { 4 int i, j, k, n; 5 6 n = num.size(); 7 for (i = 0; i < n - 1; i++) 8 { 9 k = i; 10 for (j = i + 1; j < n; j++) 11 { 12 if (num[j] < num[k]) 13 k = j; 14 } 15 16 swap(num[i], num[k]); 17 } 18 }
时间复杂度:
最好情况:O(n2) 最坏情况:O(n2) 平均情况:O(n2)
空间复杂度:O(1)
稳定性:不稳定