八大排序算法总结(带动图)
1.冒泡排序
首先冒泡排序的思路就是每次对这个序列数目的前一个元素和后一个元素进行比较,只要我的前一个数笔比后一个数大,我就将这两个数进行交换,每次只要遇到前面比后面大就进行一个交换,可想而知,每次交换出来的就是这个序列的最大值。
代码
void bubble(int* source)// 冒泡排序
{
int icount = 0;
int* arr = source;
clock_t start, finish; //开始的时间
double Times;
start = clock();
int k = 0;
for (int i = 0; i < Number; i++) //外层循环循环
{
for (int j = 0; j < Number - i - 1; j++)//每循环一趟寻找有无大小前一个比后一个大的情况
{
k++;
if (arr[j] > arr[j + 1]) //只要前面比后面大,每次都冒泡冒出一个最大的。
{
swap(arr[j], arr[j + 1]);
icount++;
}
}
}
finish = clock();
Times = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "冒泡排序所用时间:" << Times << endl;
cout << "冒泡排序所交换的次数:" << icount << endl;
cout << "冒泡排序所比较的次数:" << k << endl;
}
2.选择排序
可以想到冒泡排序的交换次数到达了排序数目的平方级别,但是,选择排序相对于冒泡排序好一点的地方就是,选择排序每进行一趟的循环把序列的第一个数当做关键码来看,同时每次在序列中找一个最比这个数的数,然后对应下标,这个下标就是关键码的下标,当我的一趟比较结束之后,如果我的关键码的下标和我的这一趟排序的第一个数的下标不相同,那么就进行一次交换,这种排序的交换次数确实是减少了,但是我的排序的时间复杂度还是和冒泡排序时一个数量级的,这个排序是稳定并且空间复杂度为1。
void choose(int source[]) {
int icount = 0;
int *temp = source;
clock_t start, finish;
double Times;
int Min,j;
start = clock();
int k=0;
for (int i = 0; i < Number-1; i++)
{
Min = i;
for (j = i + 1; j < Number-1; j++)
{
k++;
if (temp[Min] > temp[j])
Min = j;
}
if (Min != i)
{
int tem;
tem = temp[Min];//选择一个基准关键码(第一个数的下标),进行交换
temp[Min] = temp[i];
temp[i] = tem;
icount++;
}
}
finish = clock();
Times = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "选择排序所用时间:" << Times << endl;
cout << "选择排序所交换的次数:" << icount << endl;
cout << "选择排序所比较的次数:" << k << endl;
}
如图,可以看到交换次数明显下降,但是比较次数的数量级没有发生变化
3.插入排序
这种算法其实是一种进步,我们刚刚的算法其实每次双层循环的时间复杂度是比较大的,而这个排序在时间复杂度上有所降低,直接插入排序的思路是每进行一趟循环前面的i个数就是有序的,,首先选择一个关键码,一般就是每趟排序的最后一个数,只要我的这个数比前面的一个数小,就把这个位置的数往后挪动一位,那么每趟下来前几个排序的数都是有序的。但是,虽然这个排序的算法看起来比前两个快,但是它们的时间复杂度没有发生变化,还是一个数量级别上面的。
void insertion(int *source)//插入排序
{
int*temparr=source;
clock_t start,finish;
double Times;
start=clock();
int icount = 0;
int k = 0;
for(int i=0;i<Number;i++)//进入第一层排序,每进行一趟排序,前面的i个数就是有序的
{
int j = i;//标记
int x = temparr[i];//关键码
k++;
for (; j > 0 && x< temparr[j - 1];j--)//前面的j个数进行比较,只要发现后面的比前面一个大,就让前面的这个往后挪一位。
{
temparr[j] = temparr[j - 1];
icount++;
}
temparr[j] = x;
}
finish=clock();
//for (int i = 0; i < Number; i++)
// cout << temp[i] << " ";
cout << endl;
Times=(double)(finish - start) / CLOCKS_PER_SEC;
cout << "插入排序所用时间:" << Times << endl;
cout << "插入排序所交换的次数:" << icount << endl;
}
4.希尔排序
希尔排序其实是很多个的插入排序组成的,我们知道插入排序由于每次都要比较所以挪动的次数比较多,如果我们的序列数目如果比较小的话是不是就可以减少移动次数,首先让小的数先到左边,大的数到右边,这样的话最后一次进行一次插入排序,其实是可以减少移动次数的。
首先,希尔排序的思路是每次设置一个步长,我的步长每次都是除以2缩减的,当我的步长是0的时候进行一次插入排序结束循环,同时在我每次变换一次步长的时候我就从这个步长这么大的位置进入循环,将我每个分组好的序列进行一个间隔为这么大个步长的插入排序,这个希尔排序时优化的比较完整的,是不用和自己比较,所以从我循环的那个位置开始循环就可以了,只要我发现temp[j-inc]>key,我就将这两个数进行交换,好处是,当我的j不断变大时,我的比较次数会变大,但是我的交换次数会减少,因为我的序列在慢慢的变得接近有序,希尔排序的算法效率算是进行一个很好的改进了,但是它不太稳定。
void shell(int source[])
{
int *temparr;
temparr = new int[Number];
for (int i = 0; i < Number; i++)
{
temparr[i] = source[i];
}
int inc,i,j,key;
clock_t start, finish;
double Times;
start = clock();
int k = 0;
int icount = 0;
for (inc = Number / 2; inc > 0; inc /= 2) //每次的步长
{
for (i = inc; i < Number; i++) //希尔排序是分组完成的,这是优化后的代码
{
k++;
key = temparr[i]; //不用自己和自己比较,所以就从第i个开始
for (j = i; j > inc-1 && temparr[j - inc] > key; j -= inc)
{
temparr[j] = temparr[j - inc];
icount++;
}
temparr[j] = key;
//icount++;
}
}
finish = clock();
Times = (double)(finish - start) / CLOCKS_PER_SEC;
/*for (i = 0; i < Number; i++)
cout << temparr[i] << " ";*/
cout << "每次步长为2的倍数" << endl;
cout << "希尔排序所用时间:" << Times << endl;
cout << "希尔排序所交换的次数:" << icount << endl;
cout << "希尔排序的比较次数是:" << k << endl;
}
如图所示,是希尔排序的交换次数和比较次数,明显比前三个排序的时间复杂度好
5.快速排序
快速排序有很多种的思路,我用的快速排序的方法是递归的思路,将序列的第一个数当做基准数,一开始从序列的右边进行检索,如果我的右边的数比基准数大,不做任何操作,将指针向左进行移动,直到找到那个比基准数小的位置,然后从左边开始进行比较,一旦从左边找到比我的基准数大的数就将刚刚左边和右边的这两个数进行交换,直到交换进行到我的我的左下标和我的右下标一样的时候退出我的小循环,将基准数和我的这个下标位置进行交换,同时将关键码设为这个停止的位置,从这个位置将这个数组每次进行二分法再次进行如上所说的排序,直到我的这个数组左右两个数不能再比较为止。
void fast_sort(int begin, int end, int* temp)
{
if (begin >= end)//当我开始左边的指针大于右边的指针时,退出,是递归的出口
return;
int left = begin;
int right = end;
int key = begin; //关键码为开始的这个数
while (begin < end)
{
//从结尾开始进行计数
k++;
while (end > begin && temp[end] >= temp[key])//如果后面的数比基准数大,移动指针
--end;
k++;
while (end > begin && temp[begin] <= temp[key])//当前面的数比基准数小,移动指针
++begin;
swap(temp[begin], temp[end]);//比基准数小的和比基准数大的进行交换
Count++;//进行计数
}
swap(temp[end], temp[key]);//基准数和我的现在两个下标一样的位置进行交换
Count++;
key = end;//关键码指向结尾
fast_sort(left, key - 1, temp); //进入递归,左侧进行一次快速排序
fast_sort(key + 1, right, temp);//右侧进行一次快速排序 不稳定
}
void fastsort_test(int*source)
{
int* temp;
temp = new int[Number];
for (int i = 0; i < Number; i++)
{
temp[i] = source[i];
}
clock_t start, finish;
double Times;
start = clock();
fast_sort(0, Number-1, temp);
finish = clock();
Times = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "快速排序所用时间:" << Times << endl;
cout << "快速排序所交换的次数:" << Count << endl;
cout << "快速排序的比较次数是:" << k << endl;
k = 0;
Count = 0;
/*for (int i = 0; i < Number; i++)
cout << temp[i] << " ";
cout << endl;*/
}
6.堆排序
首先,这个堆排序是在数组当中建立堆的,这样进行的原因是为了减少空间复杂度,想要进行堆排序首先要建立一个最大堆,同时建立最大堆要从要从我的最后一个父节点开始进行建立,最大堆的含义就是我的父节点比我的两个子节点是大的,这个规律是从根节点开始的,最后,建立完最大堆之后,我的这个堆并不是有序的,我要每次和我的最后一个节点进行互换,第一次互换是最大的数,第二次互换是次大的数。
void heapify(int*source,int n,int i)
{
int*temp = source;
int c1 = 2 * i + 1;//找到第一个孩子
int c2 = 2 * i + 2;//第二个孩子
int Min = i;//先把最大值设定为父亲节点
if (i >= n)//递归的出口
return;
if ((c1 < n) && temp[c1] <= temp[Min])//孩子的大小如果大于父亲的大小,把最大值设定为孩子
{
Min = c1;
}
if ((c2 < n) && temp[c2] <= temp[Min])
{
Min = c2;
}
if (Min != i) //如果最大值不是父亲,那么就对这个堆进行调整
{
int t = temp[Min];
temp[Min] =temp[i];
temp[i] = t;
Count++;
heapify(temp, n, Min); //这个父亲进行调整的同时,对于它的子代也会发生影响,所以对此也进行一个排序
}
}
这个是堆排序的第一个过程就是将一个无序的序列进行堆化的过程,堆化的过程是在数组当中建立的,所以要找父节点的两个孩子,如果比父节点大,就进行交换,,同时交换完之后,这个堆是不平衡的还要再次从这个点开始再次进行堆化。
虽然堆化的函数建立完成了,但是这只是对一个数组当中的一个数进行堆化,所以要想得到最大堆,得从这个堆的最后一个父节点开始进行堆排序,每次都使一个小堆成为一个最大堆。
void bulid_heap(int*source,int n) //对于一个无序堆进行排序时要从它的最后一个父亲开始排序
{
int*temp = source;
for (int i = (n-2)/2; i >= 0; i--) //最后一个孩子直到根节点
{
heapify(temp, n, i);
}
}
第三个步骤就是有点像冒泡的过程,每次把最大堆的第一个元素冒泡到最后,同时第一个元素和最后一个元素进行交换,那么进行到最后,这个序列就是有序的。
void heap_sort(int*source, int n)
{
int*temp = source;
clock_t start, finish;
double Times;
start = clock();
bulid_heap(temp, n);
for (int i = n - 1; i >= 0; i--)
{
int t = temp[0];
temp[0] = temp[i];
temp[i] = t;
Count++;
heapify(temp, i, 0); //每次和根节点进行交换,同时交换完对n-1个孩子进行再次堆排序(交换产生的影响)
}
for (int i = 0; i < Number; i++)
cout << temp[i] << " ";
finish = clock();
Times = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "堆排序所用时间:" << Times << endl;
cout << "堆排序所交换的次数:" << Count << endl;
Count = 0;
}
7.归并排序
首先,归并排序的思路要很清楚,它是一个不断把一个序列进行一个很小的划分完之后,然后两个两个进行比较然后归并的一个思路,如图所示是归并排序的一个主要调用函数,归并排序开辟了一个和这个数组一样大小的内存,进行拷贝的作用。
如下图所示,是归并排序的准备过程,设立一个中间的数,每次进行一个划分知道我划分到我的数组每个数都是单独一个为止,因为一个数的有序序列就是它自己,所以此时,从这开始两个两个进行排序,进入真正归的归并函数。
首先,设置变量,因为是两个子序列的划分,需要从这个序列的中间位置向两边进行检索,所以,设置三个变量,标记左半区域的位置和标记右半区域的位置的变量和标记在临时数组当中位置的变量。
void merge(int arr[], int temparr[], int left, int mid, int right)
{
//标记左半区域的位置
int l_pos = left;
//标记右半区域的位置
int r_pos = mid + 1;
//标记临时数组的位置
int t_pos = left;
//合并
while (l_pos <= mid && r_pos <= right)
{
if (arr[l_pos] < arr[r_pos])
{
Count++;
temparr[t_pos++] = arr[l_pos++];
}
else
{
Count++;
temparr[t_pos++] = arr[r_pos++];
}
}
//合并左半区域的元素
while (l_pos <= mid)
{
temparr[t_pos++] = arr[l_pos++];
}
//合并右半区域的元素
while (r_pos <= right)
{
temparr[t_pos++] = arr[r_pos++];
}
//将临时数组中的元素赋值给原来的数组
while (left <= right)
{
arr[left] = temparr[left];
left++;
}
}
void msort(int arr[], int temparr[], int left, int right)
{
if (left < right)
{
int mid = (left + right) / 2; //找中间值
msort(arr, temparr, left, mid); //划分左半区域
msort(arr, temparr, mid + 1, right); //划分右半区域
merge(arr, temparr, left, mid, right);
}
}
//归并排序
void merge_sort(int arr[], int n)
{
clock_t start, finish;
double Times;
start = clock();
//分配内存,临时数组,分配成功
int* temparr = (int*)malloc(n * sizeof(int));
if (temparr)
{
msort(arr, temparr, 0, n - 1);
free(temparr);
}
//分配失败
else
{
cout << "failed to allocate memory" << endl;
}
finish = clock();
Times = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "归并排序所用时间:" << Times << endl;
cout << "归并排序所比较的次数:" << Count << endl;
Count = 0;
}
8.基数排序
void Radix_sort(int*arr) {
clock_t start, finish;
double totaltime;
int* temparr = new int[Number];
int* tmp = new int[Number];
int* count = new int[10];
int radix = 1, iK = 0;
for (int i = 0; i < Number; i++) {
temparr[i] = arr[i];
}
start = clock(); //开始计算基数排序时间
int maxDigit = MaxNum(temparr);
for (int iX = 1; iX <= maxDigit; iX++) //进行maxDigit次排序
{
for (int iJ = 0; iJ < 10; iJ++)
count[iJ] = 0; //每次分配前清空计数器
for (int iJ = 0; iJ < Number; iJ++)
{
iK = (temparr[iJ] / radix) % 10; //统计每个桶中的记录数
count[iK]++;
}
for (int iJ = 1; iJ < 10; iJ++)
count[iJ] = count[iJ - 1] + count[iJ]; //将tmp中的位置依次分配给每个桶
for (int iJ = Number - 1; iJ >= 0; iJ--) //将所有桶中记录依次收集到tmp中
{
iK = (temparr[iJ] / radix) % 10;
tmp[count[iK] - 1] = temparr[iJ];
count[iK]--;
}
for (int iJ = 0; iJ < Number; iJ++) { //将临时数组的内容复制到temp中
temparr[iJ] = tmp[iJ];
}
radix = radix * 10;
}
finish = clock();
totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "基数排序所用时间:" << totaltime << endl;
cout << "基数排序交换次数:" <<Count;
Count = 0;
}
最终实现的main函数
#include"paixu.h"
#include<iostream>
#include<time.h>
using namespace std;
int* temp,*temp2,*temp3,*temp4;
void resize()
{
srand((unsigned int)time(0));
temp = new int[Number];//得到随机数组
for (int X = 0; X < Number; X++)
{
temp[X] = rand() % 5000000;
}
}
int main(){
cout << "** 排序算法比较 **\n";
cout << "============================================================\n";
cout << "** 1 --- 冒泡排序 **\n";
cout << "** 2 --- 选择排序 **\n";
cout << "** 3 --- 直接插入排序 **\n";
cout << "** 4 --- 希尔排序 **\n";
cout << "** 5 --- 快速排序 **\n";
cout << "** 6 --- 堆排序 **\n";
cout << "** 7 --- 归并排序 **\n";
cout << "** 8 --- 基数排序 **\n";
cout << "** 9 --- 退出程序 **\n";
cout << "============================================================\n";
tt:cout << "请输入要产生的随机数个数:";
cin>>Number;
if (Number < 0)
{
cout << "输入错误,请输入大于0的数"<<endl;
goto tt;
}
if (cin.fail())
{
cin.clear();
cin.ignore();
cout << "输入非法,请重新输入" << endl;
goto tt;
}
int choice;
mm:cout << "请选择排序算法:";
cin >> choice;
cout << endl;
while (choice != 9)
{
if (cin.fail())
{
cin.clear();
cin.ignore();
//cin.clear();
cout << "输入非法,请重新输入" << endl;
cout << endl;
goto mm;
}
switch (choice)
{
case 1:
resize();
bubble(temp);
cout << endl;
break;
case 2:
resize();
choose(temp);
cout << endl;
break;
case 3:
resize();
insertion(temp);
cout << endl;
break;
case 4:
resize();
shell(temp);
cout << endl;
break;
case 5:
resize();
fastsort_test(temp);
cout << endl;
break;
case 6:
resize();
heap_sort(temp, Number);
cout << endl;
break;
case 7:
resize();
merge_sort(temp, Number);
cout << endl;
break;
case 8:
resize();
Radix_sort(temp);
cout << endl;
break;
case 10:
//resize();
fastsort_test1(temp);
break;
case 11:
//resize();
shell6(temp);
break;
case 12:
//resize();
shell5(temp);
break;
case 13:
shell3(temp);
break;
default:
cout << "输入有误,请重新输入" << endl;
break;
}
cout << "请选择排序算法:";
cin >> choice;
}
system("pause");
return 0;
}