排序算法有:冒泡排序,快速排序,选择排序,堆排序,插入排序,合并排序,希尔排序,计数排序,基数排序,桶排序。
交换排序: 冒泡排序、快速排序
选择排序:直接选择排序、树形选择排序、堆排序
插入排序: 直接插入排序、希尔排序、合并排序
分配排序: 桶排序、基数排序、计数排序
交换排序
1、冒泡排序:
对待排序元素的关键字从后往前进行多遍扫描,遇到相邻两个关键字次序与排序规则不符时,就将这两个元素进行交换。这样关键字较小的那个元素就像一个泡泡一样,从最后面冒到最前面来。
#include <iostream>
using namespace std;
void BubbleSort(int a[], int n) {
for (int i = 0; i < n; i++) //遍历n次
for (int j = n-1; j > i; j--) {
if (a[j] < a[j-1]) { //当前比较前面键值,使当前总为最小的
swap(a[j-1], a[j]);//交换
}
}
}
int main()
{
int num[6] = {23,45,13,2,99,78};
cout << "冒泡排序前:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
BubbleSort(num, 6);
cout << "冒泡排序后:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
return 0;
}
冒泡的时间复杂度为O(n2),空间复杂度O(1)。稳定。
快速排序采用了分治算法策略,它是冒泡排序的一种改进。
基本思路是:把待排列的数据分为两个子列,从数列中挑出一个数作为基准,遍历其他数据,把小于它的放前面,大的放在基准的后面。之后,通过递归,将各个子序列划分为更小的序列,直到把小于基准值元素的子数列和大于基准值元素的字数列排序。
#include <iostream>
using namespace std;
//拆分为两个子列
int Partition(int a[], int left, int right) {
int base = a[left];
while (left < right) {
while (left < right && a[right]>base) //从右往左找出第一个比基准小的数据
--right;
a[left] = a[right]; //将这个数放到基准的左边
while (left < right && a[left]<base) //从左往右找出第一个比基准大的数据
++left;
a[right] = a[left]; //放到右边
}
a[left] = base;
return left; //返回基准的位置
}
void QuickSort(int a[], int left, int right) {
int i;
if (left < right) {
i = Partition(a, left, right);
QuickSort(a, left, i-1);
QuickSort(a, i+1, right);
}
}
int main()
{
int num[6] = {23,45,13,2,99,78};
cout << "排序前:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
QuickSort(num, 0, 5);
cout << "排序后:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
return 0;
}
</pre><pre name="code" class="cpp">
<strong><span style="font-size:32px;">选择排序</span></strong>
<strong><span style="font-size:32px;">
</span></strong>
<span style="font-size:32px;"></span><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;">1,直接选择排序:</p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;">对待排序的序列,选出关键字最小的数据,将它和第一个位置的数据交换,接着,选出关键字次小的数据,将它与第二个位置上的数据交换。以此类推,直到完成整个过程。</p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;">所以如果有n个数据,那个需要遍历n-1遍。其实现代码如下:</p><strong>
</strong>
#include <iostream>
using namespace std;
void SelectSort(int a[], int n) {
int i, j, small;
for (i = 0; i < n-1; ++i) {
small = i;
for (j = i+1; j < n; ++j) {
if (a[small] > a[j]) small = j;
}
if (small != i)
swap(a[small], a[i]);
}
}
int main()
{
int num[6] = {23,45,13,2,99,78};
cout << "排序前:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
SelectSort(num, 6);
cout << "排序后:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
return 0;
}
直接选择排序的时间复杂度为O(n2)。同样适合对链式结构进行排序
2、堆排序
#include <iostream>
using namespace std;
//用数组二叉树
void HeapAdjust(int a[], int s, int n)//构成堆
{
int j;
while(2*s + 1 < n) //第s个结点有右子树
{
j=2*s + 1 ;
if((j+1) < n)
{
if(a[j] < a[j+1])//右左子树小于右子树,则需要比较右子树
j++; //序号增加1,指向右子树
}
if(a[s] < a[j])//比较s与j为序号的数据
{
swap(a[s], a[j]);
s = j ;//堆被破坏,需要重新调整
}
else //比较左右孩子均大则堆未破坏,不再需要调整
break;
}
}
void HeapSort(int a[],int n)//堆排序
{
int t, i;
int j;
for(i = n/2 - 1; i >= 0; i--) //将a[0,n-1]建成大根堆
HeapAdjust(a, i, n);
for(i = n-1; i > 0; i--)
{
swap(a[0], a[i]);
HeapAdjust(a, 0, i); //将a[0]至a[i]重新调整为堆
}
}
int main()
{
int num[6] = {23,45,13,2,99,78};
cout << "排序前:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
HeapSort(num, 6);
cout << "排序后:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
return 0;
}
堆排序的时间复杂度为O(nlog2n),空间复杂度为O(1)。不稳定。
插入排序
1,直接插入排序
直接插入排序是一种简单直观的排序算法,它的工作原理是通过建有序序列,对于没有排序的数据,在已排序序列中从后往前扫描,找到相应位置,并插入。故,如果是数组这样的连续空间的数据序列,那就每次插入都要将其位置的后面数据都向后移动,相对比较麻烦。
#include <iostream>
using namespace std;
void InsertSort(int a[], int n) {
int i, j, temp;
for (i = 1; i < n; ++i) {
temp = a[i]; //先保存当前值
for (j = i-1; j >= 0 && temp < a[j]; --j) //从后往前移,直到找到适合位置
a[j+1] = a[j]; //往后移一位,腾出位置
a[j+1] = temp; //将值放入已找出的适当位置
}
}
int main()
{
int num[6] = {23,45,13,2,99,78};
cout << "排序前:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
InsertSort(num, 6);
cout << "排序后:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
return 0;
}
这样的直接插入排序空间复杂度O(1),但是时间复杂度为O(n2)
2、希尔排序
希尔排序(Shell Sort)是插入排序的一种。是针对直接插入排序算法的改进。该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。
它的具体做法是:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成(n除以d1)个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
#include <iostream>
using namespace std;
void ShellSort(int a[], int n) {
int d, i, j, temp;
d = n/2; //分成n/2组
while (d >= 1) {
for (i = d; i < n; ++i) { //对每组进行直接插入排序
temp = a[i];
j = i - d;
while (j >= 0 && a[j] > temp) {
a[j+d] = a[j];
j -= d;
}
a[j+d] = temp;
}
d /= 2;
}
}
int main()
{
int num[6] = {23,45,13,2,99,78};
cout << "排序前:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
ShellSort()
cout << "排序后:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
return 0;
}
希尔排序时间复杂度为O(n4/3),空间复杂度为O(1),但是不适合在链表结构上使用。3、合并排序(归并排序)
合并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。合并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。合并排序也叫归并排序。
<pre name="code" class="cpp">#include <iostream>
using namespace std;
//2-路合并
void Merge(int a[], int r[], int left, int mid, int n) {//a[]归并到r[]
int s1 = left, s2 = mid+1, s3 = left;
while (s1 <= mid && s2 <= n)//比较较小的数据填充到a[]
if (a[s1] <= a[s2])
r[s3++] = a[s1++];
else
r[s3++] = a[s2++];
while (s1 <= mid) r[s3++] = a[s1++];//将未填充的数补全
while (s2 <= n) r[s3++] = a[s2++];//将未填充的数补全
}
//2-路归并排序
void MergePass(int a[], int r[], int n, int len) {
int beg = 0, end;
while (beg + len < n) {
end = beg + 2*len - 1;
if (end >= n)//最后一个可能少于len个
end = n - 1;
Merge(a, r, beg, beg+len-1, end);//合并
beg = end + 1;//提供给下次开始
}
if (beg < n)
while (beg < n) {
r[beg] = a[beg];
beg++;
}
}
void MergeSort(int a[], int n) {
int len = 1;//当前进行归并有序数列的长度
int f = 0;
int *p = (int *)malloc(sizeof(int)*n);
while(len < n) {
if (f) MergePass(p, a, n, len);//交替归并到p和a
else MergePass(a, p, n, len);
len *= 2;
f = 1 - f;
}
if (f) //排序后是归并到p的情况,复制回到a
for (f = 0; f < n; f++)
a[f] = p[f];
free(p);
}
int main()
{
int num[6] = {23,45,13,2,99,78};
cout << "排序前:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
MergeSort(num, 6);
cout << "排序后:" << endl;
for (int i = 0; i < 6; i++) {
cout << num[i] << " ";
}
cout << endl;
return 0;
}
分配排序
桶排序:
假定:输入是由一个随机过程产生的[0, 1)区间上均匀分布的实数。将区间[0, 1)划分为n个大小相等的子区间(桶),每桶大小1/n:[0, 1/n), [1/n, 2/n), [2/n, 3/n),…,[k/n, (k+1)/n ),…将n个输入元素分配到这些桶中,对桶中元素进行排序,然后依次连接桶输入0 ≤A[1..n] <1辅助数组B[0..n-1]是一指针数组,指向桶(链表)。
他的排序过程如图:
桶排序只是用于关键字取值范围较小的情况,否则会因为所需箱子的数目太多而导致资源的浪费。这种排序的使用价值不大,他一般用于基数排序的一个中间过程。
2,基数排序:
基数排序是桶排序的一种改进和推广。它的基本思想是,先设立r个队列,队列编号分别为0~r-1,(r为关键字的基数),然后按照下面的规则对关键字进行“分配”和“收集”。
1, 按照最低有效位的值,把n个关键字分配到上述的r个队列里,然后从小到达将各队列中关键字收集起来。
2, 再按低次有效位的值把刚刚收集起来的关键字分配到r个队列中,重复收集工作。
3, 重复上述分配和收集工作,直到最高的有效位。(也就是说,如果数位为d,则需要重复进行d次。d由所有元素中最长的一个元素的位数计量。)
图示如下:
上图过程,就是先按个位分配然后收集,再按十位,百位分配和收集,最后就得出排序结果。
在C++中可以使用库函数lexicongraphical_compare()进行字典次序比较。
每一趟分配的时间是O(n),所以总时间的开销为O(d(n+r)) = O(n),通常d,r为常数。空间负复杂度为O(n+r)。基数排序使用于采用链式结构存储结构的排序。
3,计数排序:
计数排序是一个类似于桶排序的排序算法,其优势是对已知数量范围的数组进行排序。它创建一个长度为这个数据范围的数组C,C中每个元素记录要排序数组中对应记录的出现个数。这个算法于1954年由 Harold H. Seward 提出。
计数排序算法的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。
#include <iostream>
using namespace std;
//n为个数,k为最大值,b为输出
void CountingSort(int a[], int b[], int n, int k)
{
int* c = new int[k+1];
memset(c, 0, (k+1) * sizeof(int));
for (int j = 0; j < n; j++) c[a[j]]++;//保存每个下标的值的个数
for (int j = 1; j <= k; j++) c[j] += c[j - 1];//从前到后累计的位置
for (int j = n - 1; j >= 0; j--)
{
b[c[a[j]] - 1] = a[j];
c[a[j]]--;
}
delete []c;
}
int main()
{
int num[6] = {23,45,13,2,99,78};
int out[6];
cout << "排序前:" << endl;
int max = 0;
for (int i = 0; i < 6; i++) {
if (max < num[i]) max = num[i];
cout << num[i] << " ";
}
cout << endl;
CountingSort(num, out, 6, max);
cout << "排序后:" << endl;
for (int i = 0; i < 6; i++) {
cout << out[i] << " ";
}
cout << endl;
return 0;
}