C++ 常用的八种排序方法
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503121645474.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNTg3MzQ1,size_16,color_FFFFFF,t_70#pic_center)
稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同
插入排序:
思想:
(类似打扑克时的排序方法)
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
优点:稳定,快
缺点:比较次数不一定
动态图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200502130448784.gif#pic_center)
程序:
//直接插入排序
//将数组中的所有元素依次和前面的已经排好序的元素相比较(依次),
//如果选择的元素比已排序的元素小,则交换位置,直到全部元素都比较过。
#include<iostream>
using namespace std;
void work1(int *a,int size)
{
for (int i = 0; i < size-1; i++)
{
int end;
end = i + 1;
while (end>0)
{
if (a[end - 1] > a[end])
{
int temp = a[end];
a[end] = a[end - 1];
a[end - 1] = temp;
end--;
}
else
{
break;
}
}
}
}
void test()
{
int a[] = { 7,3 ,2,9,10,6,4};
int a_size;
a_size = sizeof(a) / sizeof(int);
work1(a,a_size);
for (int i = 0; i < a_size; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
int main()
{
test();
return 0;
}
shell排序:
思想:
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
动态图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503003732567.gif#pic_center)
程序:
//希尔排序(直接插入法排序的改进)
//将数组按一定的间隔分成数个小数组,在小数组中使用直接插入法排序,然后修改间隔使其间隔
//变小,将数组再次重新分成几个小数组使用直接插入法排序,依此下去,直到间隔缩小到1为止
//直接插入法排序是希尔排序间隔为1时的特例,
#include<iostream>
using namespace std;
void work1(int *a, int size)
{
int h = 1;
while (h < size / 3)
{
h = 3 * h + 1;
}
while (h >= 1)
{
for (int i = h; i < size; i++)
{
for (int j = i; j >= h && a[j] < a[j - h]; j -= h)
{
std::swap(a[j], a[j - h]);
}
}
h = h / 3;
}
}
void test()
{
int a[] = { 7,3,15,9,4,2,12,8};
int a_size;
a_size = sizeof(a) / sizeof(int);
work1(a, a_size);
for (int i = 0; i < a_size; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
int main()
{
test();
return 0;
}
直接选择排序:
思想:
1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3.重复第二步,直到所有元素均排序完毕。
动态图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503003841651.gif#pic_center)
程序:
//选择排序
//第一轮:将第一个元素视为最小值,用第一元素个依此与后面的元素做比较,有比第一个元素小的时交换位置
//第二轮:将第二个元素视为最小值,用第二元素个依此与后面的元素做比较,有比第一个元素小的时交换位置
#include<iostream>
#include<vector>
using namespace std;
void work1(vector<int> & a, int size)
{
for (int i = 0; i < size; i++)
{
int min = i;
for (int j = i+1; j < size; j++)
{
if (a[min]>a[j])
{
min = j;
}
}
int temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
void test()
{
vector<int> a;
a.push_back(7);
a.push_back(17);
a.push_back(72);
a.push_back(8);
a.push_back(4);
a.push_back(9);
a.push_back(16);
a.push_back(28);
work1(a, a.size());
for (int i = 0; i < a.size(); i++)
{
cout << a[i] << " ";
}
cout << endl;
}
int main()
{
test();
return 0;
}
堆排序:
思想:
堆排序是基于选择排序的一种排序算法,堆是一个近似完全二叉树的结构,且满足子结点的键值或索引总是小于(或者大于)它的父节点。这里采用最大堆方式:位于堆顶的元素总是整棵树的最大值,每个子节点的值都比父节点小,堆要时刻保持这样的结构,所以一旦堆里面的数据发生变化,要对堆重新进行一次构建。
动态图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503003932382.gif#pic_center)
程序:
//堆排序
//堆排序是基于选择排序的一种排序算法,堆是一个近似完全二叉树的结构,
//且满足子结点的键值或索引总是小于(或者大于)它的父节点。
#include <iostream>
#include <algorithm>
using namespace std;
void max_heapify(int arr[], int start, int end)
{
//建立父节点指标和子节点指标
int dad = start;
int son = dad * 2 + 1;
while (son <= end) //若子节点指标在范围内才做比较
{
if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的
son++;
if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数
return;
else // //否则交换父子内容再继续子节点和孙节点比较
{
swap(arr[dad], arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len) {
//初始化,i从最後一个父节点开始调整
for (int i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
//先将第一个元素和已经排好的元素前一位做交换,再从新调整(刚调整的元素之前的元素),直到排序完毕
for (int i = len - 1; i > 0; i--)
{
swap(arr[0], arr[i]);
max_heapify(arr, 0, i - 1);
}
}
int main() {
int arr[] = { 9,7,8,5,3, 4, 0, 2, 6 };
int len = (int) sizeof(arr) / sizeof(*arr);
heap_sort(arr, len);
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
return 0;
}
冒泡排序:
思想:
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
优点:稳定
缺点:慢,每次只能移动两个相邻的数据;
动态图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503004030300.gif#pic_center)
程序:
//冒泡排序
//比较相邻的元素。如果第一个比第二个大,就交换他们两个。
//对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
//针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
#include<iostream>
using namespace std;
void work1(int *a, int size)
{
for (int j = 0; j < size-1; j++)
{
for (int i = 1; i < size-j; i++)
{
if (a[i - 1] > a[i])
{
int temp = a[i - 1];
a[i - 1] = a[i];
a[i] = temp;
}
}
}
}
void test()
{
int a[] = { 17,3 ,42,39,10,6,64,72,48 };
int a_size;
a_size = sizeof(a) / sizeof(int);
work1(a, a_size);
for (int i = 0; i < a_size; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
int main()
{
test();
return 0;
}
快速排序:
思想:
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
动态图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503004117832.gif#pic_center)
程序:
//快速排序
//思想是每趟排序时选取一个数据(通常用数组的第一个数)作为关键数据,
//然后将所有比它小的数都放到它的左边,所有比它大的数都放到它的右边.
#include<iostream>
#include<algorithm>
using namespace std;
int Paritition1(int A[], int low, int high)
{
int pivot = A[low];
while (low < high)
{
while (low < high && A[high] >= pivot)
{
--high;
}
A[low] = A[high];
while (low < high && A[low] <= pivot)
{
++low;
}
A[high] = A[low];
}
A[low] = pivot;
return low;
}
void QuickSort(int A[], int low, int high) //快排母函数
{
if (low < high)
{
int pivot = Paritition1(A, low, high);
QuickSort(A, low, pivot - 1);
QuickSort(A, pivot + 1, high);
}
}
void test()
{
int a[] = { 7,3 ,4,9,1,6,5,2,0 };
int a_size;
a_size = sizeof(a) / sizeof(int);
QuickSort(a,0, 8);
for (int i = 0; i < a_size; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
int main()
{
test();
return 0;
}
归并排序:
思想:
归并排序的思想是将两个有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。即先划分为两个部分,最后进行合并。
动态图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503004159664.gif#pic_center)
程序:
//归并排序
//首先将初始序列的n个元素看成是n个有序的子序列,每个子序列的长度为1,
//然后两两归并,得到n/2个长度为2的有序子序列,在此基础上,再对长度为2
//的有序子序列进行两两归并,得到若干个长度为4的有序子序列,以此类推,直
//到得到一个长度为n的有序序列为止
#include<iostream>
#include<algorithm>
using namespace std;
void work1(int *ar, int size)
{
int *a = ar;
int *b = new int[size];
for (int seg = 1; seg < size; seg += seg)
{
for (int start = 0; start < size; start += seg + seg)
{
int low = start, mid = min(start + seg, size), high = min(start + seg + seg, size);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
while (start1 < end1 && start2 < end2)
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
while (start1 < end1)
b[k++] = a[start1++];
while (start2 < end2)
b[k++] = a[start2++];
}
int *temp = a;
a = b;
b = temp;
}
if (a != ar)
{
for (int i = 0; i < size; i++)
b[i] = a[i];
b = a;
}
delete[] b;
}
void test()
{
int a[] = { 17,3 ,42,39,10,6,64,72,48 };
int a_size;
a_size = sizeof(a) / sizeof(int);
work1(a, a_size);
for (int i = 0; i < a_size; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
int main()
{
test();
return 0;
}
//第一步:先将相邻的两个元素两两排序,
//第二步:将相邻的四个元素两两排序,
//第三步:将相邻的八个元素两两排序,
// .........
// .........
//依此类推
基数排序:
思想:
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
动态图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020050300423976.gif#pic_center)
程序:
//基数排序
//是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,
//然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)
//和特定格式的浮点数,所以基数排序也不是只能使用于整数。
#include <iostream>
#include <algorithm>
using namespace std;
int maxbit(int data[], int n) //辅助函数,求数据的最大位数
{
int maxData = data[0]; ///< 最大数
/// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。
for (int i = 1; i < n; ++i)
{
if (maxData < data[i])
maxData = data[i];
}
int d = 1;
int p = 10;
while (maxData >= p)
{
maxData /= 10;
++d;
}
return d;
}
void radixsort(int data[], int n) //基数排序
{
int d = maxbit(data, n);
int *tmp = new int[n];
int *count = new int[10]; //计数器
int i, j, k;
int radix = 1;
for (i = 1; i <= d; i++) //进行d次排序
{
for (j = 0; j < 10; j++)
count[j] = 0; //每次分配前清空计数器
for (j = 0; j < n; j++)
{
k = (data[j] / radix) % 10; //统计每个桶中的记录数
count[k]++;
}
for (j = 1; j < 10; j++)
count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
for (j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
{
k = (data[j] / radix) % 10;
tmp[count[k] - 1] = data[j];
count[k]--;
}
for (j = 0; j < n; j++) //将临时数组的内容复制到data中
data[j] = tmp[j];
radix = radix * 10;
}
delete[]tmp;
delete[]count;
}
int main() {
int arr[] = { 67,9,7,8,5,3, 4, 0, 2, 6,72,39 };
int len = (int) sizeof(arr) / sizeof(*arr);
radixsort(arr, len);
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
return 0;
}