十大经典排序算法——基于STL的C++实现

目录

1、冒泡排序

2、插入排序

3、选择排序

4、希尔排序

5、归并排序

6、快速排序

7、堆排序

8、计数排序

9、桶排序

10、基数排序


十种常见排序算法可以分为两大类:

  • 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  • 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

相关概念:

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
  • 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
  • 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数

复杂度与稳定性总览

排序方法 时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
冒泡排序 O(n2) O(n2) O(n) O(1) 稳定
插入排序 O(n2) O(n2) O(n2) O(1) 稳定
选择排序 O(n2) O(n2) O(n) O(1) 不稳定
希尔排序 O(n1.3) O(n2) O(n) O(1) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
快速排序 O(nlogn) O(n2) O(nlogn) O(nlogn) 不稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 稳定
计数排序 O(n+k) O(n+k) O(n+k) O(n+k) 稳定
桶排序 O(n+k) O(n2) O(n) O(n+k) 稳定
基数排序 O(n*k) O(n*k) O(n*k) O(n+k) 稳定

1、冒泡排序

  • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;

  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;

  • 针对所有的元素重复以上的步骤,除了最后一个;

  • 重复步骤1~3,直到排序完成。

 void bubbleSort(vector& v) { 
     int n = v.size();
     for (int i = 0; i < n; i++) {
         for (int j = 0; j < n - i - 1; j++) {
             if (v[j] > v[j + 1]) {
                 int temp = v[j];
                 v[j] = v[j + 1];
                 v[j + 1] = temp;
             }
         }
     }
 }

2、插入排序

  • 从第一个元素开始,该元素可以认为已经被排序;

  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;

  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;

  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

  • 将新元素插入到该位置后;

  • 重复步骤2~5。

 void insertSort(vector& v) { 
     int n = v.size();
     for (int i = 1; i < n; i++) {
         int temp = v[i];
         for (int j = i; j > 0 && v[j] < v[j - 1]; j--) {
             v[j] = v[j - 1];
             v[j - 1] = temp;
         }
     }
 }

3、选择排序

  • 初始状态:无序区为R[1..n],有序区为空;

  • 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;

  • n-1趟结束,数组有序化了。

 void selectSort(vector& v) { 
     int n = v.size();
     for (int i = 0; i < n; i++) {
         int minIndex = i;
         int temp = v[i];
         for (int j = i + 1; j < n; j++) {
             if (v[j] < v[minIndex]) minIndex = j;
         }
         v[i] = v[minIndex];
         v[minIndex] = temp;
     }
 }

4、希尔排序

  • 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;

  • 按增量序列个数k,对序列进行k 趟排序;

  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

 void shellSort(vector& v) { 
     int n = v.size();
     for (int gap = n / 2; gap > 0; gap /= 2) {
         for (int i = gap; i < n; i++) {
             int temp = v[i];
             for (int j = i; j >= gap && temp < v[j - gap]; j -= gap) {
                     v[j] = v[j - gap];
                     v[j - gap] = temp;
             }
         }
     }
 }

5、归并排序

  • 把长度为n的输入序列分成两个长度为n/2的子序列;

  • 对这两个子序列分别采用归并排序;

  • 将两个排序好的子序列合并成一个最终的排序序列。

 void merge(vector& v, int left, int mid, int right) { // 归并函数
     vector temp = v;
     int i = left, j = mid + 1, k = left;
     
     while (i <= mid && j <= right) {
         if (temp[i] < temp[j]) v[k++] = temp[i++];
         else v[k++] = temp[j++];
     }
     while (i <= mid) v[k++] = temp[i++];
     while (j <= right) v[k++] = temp[j++];
 }
     
 void mergeSort(vector& v, int low, int high) {
     // 左闭右闭进行归并
     if (low < high) {   
         int mid = (low + high) / 2;
         mergeSort(v, low, mid);
         mergeSort(v, mid + 1, high);
         merge(v, low, mid, high);
     }
 }

6、快速排序

  • 从数列中挑出一个元素,称为 “基准”(pivot);

  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

 void quickSort(vector& v, int low, int high) {
     // 左闭右闭进行排序
     if (low >= high) return; // 递归终止条件
     int left = low, right = high; // 分别为低位下标和高位下标
     int key = v[low]; // 基准值
 ​
     while (left < right) {
         // 由高位起寻找,将小于基准值的移至前面
         while (left < right && v[right] >= key) right--;
         if (left < right) v[left++] = v[right];
         // 由低位起寻找,将大于基准值的移至后面
         while (left < right && v[left] <= key) left++;
         if (left < right) v[right--] = v[left];
     }
     // 此时left指向坐标之前数值均小于等于基准值
     // 其之后数值均大于等于基准值
     v[left] = key;
     quickSort(v, low, left - 1); // 递归处理前半部分
     quickSort(v, left + 1, high); // 递归处理后半部分
 }

7、堆排序

  • 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;

  • 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];

  • 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

 void heapify(vector& v, int n, int root) { // 堆化函数
     int maxIndex = root;
     int left = root * 2 + 1, right = root * 2 + 2;
 ​
     if (left < n && v[left] > v[maxIndex]) maxIndex = left;
     if (right < n && v[right] > v[maxIndex]) maxIndex = right;
     if (maxIndex != root) { // 需要调整根节点
         swap(v[root], v[maxIndex]);
         heapify(v, n, maxIndex);
     }
 }
     
void heapifySort(vector& v) { // 通过建立大顶堆进行排序
     // 默认已建立以v为层序遍历的堆
     // 建立大顶堆
     int n = v.size();
     for (int node = (n - 2) / 2; node >= 0; node--) {
         heapify(v, n, node);
     }
     // 根据大顶堆进行排序
     for (int i = 0; i < n; i++) {
         swap(v.front(), v[n - i - 1]);
         heapify(v, n - i - 1, 0);
     }
 }

8、计数排序

  • 找出待排序的数组中最大和最小的元素;

  • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;

  • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

 void countingSort(vector& v) {
     int max = *std::max_element(v.begin(), v.end());
     vector bucket(max + 1, 0);
     // 统计键值出现的次数
     for (int i = 0; i < v.size(); i++) {
         bucket[v[i]]++;
     }
     // 将排序结果写入v
     for (int bucketIndex = 0, vIndex = 0;
         bucketIndex < bucket.size();
         bucketIndex++) {
         while (bucket[bucketIndex]) {
             v[vIndex++] = bucketIndex;
             bucket[bucketIndex]--;
         }
     }
 }

9、桶排序

  • 设置一个定量的数组当作空桶;

  • 遍历输入数据,并且把数据一个一个放到对应的桶里去;

  • 对每个不是空的桶进行排序;

  • 从不是空的桶里把排好序的数据拼接起来。

void bucketSort(vector& v, int bucketSize = 5) {
     int minVal = *std::min_element(v.begin(), v.end());
     int maxVal = *std::max_element(v.begin(), v.end());
 ​
     // 初始化桶
     int bucketCount = (maxVal - minVal) / bucketSize + 1;
     vector> bucket(bucketCount);
     // 将数据映射到每个桶
     for (int i = 0; i < v.size(); i++) {
         bucket[(v[i] - minVal) / bucketSize].push_back(v[i]);
     }
 ​
     int vIndex = 0;
     for (int j = 0; j < bucket.size(); j++) {
         // 对每个桶内数据排序
         insertSort(bucket[j]); // 这里调用了前面的插入排序
         // 将排序后数据写入v
         for (const int& num : bucket[j]) {
             v[vIndex++] = num;
         }
     }
 }

10、基数排序

  • 取得数组中的最大数,并取得位数;

  • arr为原始数组,从最低位开始取每个位组成radix数组;

  • 对radix进行计数排序(利用计数排序适用于小范围数的特点)。

 void radixSort(vector& v) {
     int maxValue = *std::max_element(v.begin(), v.end());
     int d = 0; // 最大值的位数
     int mod = 10, dev = 1; // 用于在排序中求得对应位数的数值
     // 计算最大值位数
     while (maxValue) {
         d++;
         maxValue /= 10;
     }
     // 进行d次排序
     for (int i = 0; i < d; i++, mod *= 10, dev *= 10) {
         // 桶
         vector> buckets(10);
         // 将数据分到桶中
         for (int j = 0; j < v.size(); j++) {
             int bucket = v[j] % mod / dev;
             buckets[bucket].push_back(v[j]);
         }
         // 排序后重新写入v
         int vIndex = 0;
         for (int j = 0; j < buckets.size(); j++) {
             for (const int& num : buckets[j]) {
                 v[vIndex++] = num;
             }
         }
     }
 }

巨人的肩膀:

十大经典排序算法(动图演示) - 一像素 - 博客园

必备算法基础 | 阿秀的学习笔记

你可能感兴趣的:(排序算法,c++,算法,数据结构)