八大排序
一、冒泡排序:
<1>时间复杂度:O(n^2);
(1)最差情况:O(n^2) 无序
(2)最好情况:O(n) 有序
<2>空间复杂度:O(1);
<3>稳定性:稳定排序。 例如:5、5、3 第一趟之后为5、3、5 第一个5仍然在第二个5后面
<4>基本思想:将相邻的两个数比大小,将小的调到前面。
例如:有一行数分别是26、10,83,56,28,66,7
//方框数为比较的次数
(1)第一躺排序:
此时已将此行数中最大的那个数字放在末尾。
(2)第二躺排序:
此时将倒数第二大的数字放在后面。
(3)第三趟排序:
此时将倒数第三大的数字放在后面。
以此类推,可以得到最终结果为:7、10、26、28、56、66、83。
备注:n个数必要比较n - 1趟,第一躺比较n-1次,第 j 趟需要比较n - j 次。
<5>代码实现:
1.无序情况:
void Bubble_Sort(int *arr,int len) //其中len是数组的长度
{
for(int i = 0;i < len - 1;i++) //i表示趟数
{
for(int j = 0;j < len-i-1;j++) //j表示次数
{
if(arr[j] > arr[j+1])
{
int temp; //交换
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
2.有序情况:
void Butter_Sort2(int *arr,int len)
{
bool swap = false; //设置一个标志位
for(int i = 0;i < len - 1;i++) //i表示趟数
{
for(int j = 0;j < len-i-1;j++) //j表示次数
{
if(arr[j] > arr[j+1])
{
int temp; //交换
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
swap = true; //标志位置为true
}
}
if(!swap) //当一次躺比较没有一次进入第一个if语句时,才会
{ //进入到这个if语句,说明此时序列已经是有序数列
return;
}
}
}
二、 选择排序
<1>时间复杂度:O(n^2);
<2>空间复杂度:O(1);
<3>稳定性:不稳定排序。 例如:5、5、3 在第一次排序后变为 3、5、5,第一个5的位置已经发生改变
<4>基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
例如:88、5、15、56、32、18、69
备注:其中每条彩色线条为两数之间的比较,其条数为比较次数.图示为比较结果,过程较为简单,请读者自行分析.
(1)第一躺比较:
此时最小数5已经放在最开始的位置。
(2)第二趟比较:
此时将第二小的数字放在当前序列的开头
(3)第三趟排序:
此时将第三小的数字放在当前序列的开头
以此类推,可得到最终结果为:5、15、18、32、56、69、88
代码实现:
void Selset_Sort(int *arr,int len)
{
for(int i = 0;i < len;i++ )
{
for(int j = i+1;j < len;j++)
{
if(arr[i] > arr[j])
{
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
三.直接插入排序
<1>时间复杂度:O(n^2)
(1)最差情况:O(n^2) 无序
(2)最好情况:O(n) 有序 越有序越快
<2>空间复杂度:O(1)
<3>稳定性:稳定排序
<4>基本思想:如同打扑克牌一样,起初在摸牌的时候,第一张牌对于自身而言是有序的,假如摸到4,当第二次摸到6时,会将6放在4的后面,第三次摸到3时,会将3放在4的前面,这个过程就是直接插入排序。那么对于6和3而言,如何知道是大还是小呢?过程很简单,每摸到一张牌就和前面已有序数列作比较比较,然后交换顺序即可。
例如:8、45、18、33、15、99、3
具体实现步骤如下:设置一个临时变量temp,从二个数开始,相当于摸第二张牌,然后从一直牌开始排序。
备注:1.其中i表示当前所摸的牌,j表示已有序数列的最后一张牌,从后往前比较。
2.其中蓝色空格为temp的值,不代表方框中的值,彩色线条为两数之间的比较.
(1)第一趟排序:
(2)第二趟排序:
以此类推…
(3)第六趟排序:
此时待排序列已全部有序。
拓展:n个数排序需要n - 1躺。
代码实现:
void Insert_sort(int *arr,int len)
{
for(int i = 1;i < len;i++)
{
int temp = arr[i];
int j = 0;
for(j = i-1;j >=0;j--)
{
if(arr[j] > temp)
{
arr[j+1] = arr[j];
}
else
{
break;
}
}
arr[j+1] = temp;
}
}
四、快速排序
<1>时间复杂度:O(nlog2n)
(1)最好情况: O(nlog2n)
(2)最坏情况: O(n^2) 整个序列以及完全有序且为倒序,退化为冒泡排序
<2>空间复杂度:O(log2n)
<3>稳定性:不稳定排序
<4>基本思想:通过一趟排序将要排序的数据分割成独立的两部分,将序列分为两部分的数作为基准,基准左边的数都要比基准右边的数要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
例如:8,15,26,18,7,66,4
分析:
依靠基准将序列分割为两部分,那么基准如何找到呢?
方法有三种:
1.固定位置选取基准法
2.随机选取基准法
3.三分取中法
<5>具体步骤:
一次取基准(par)过程:
然后对左右两部分子序列继续进行此步骤。(如果子序列为单个数时,说明自身有序,不进行此过程)
代码实现:
int Partion(int *arr,int low,int high) //一次找基准过程
{
int temp = arr[low];
while(low < high) //当low = high时退出循环,此时的位置为基准位置
{
while(low < high && arr[high] > temp)
{
high--;
}
if(low >= high)
{
break;
}
else
{
arr[low] = arr[high];
}
while(low < high && arr[low] < temp)
{
low++;
}
if(low >= high)
{
break;
}
else
{
arr[high] = arr[low];
}
}
arr[low] = temp;
return low;
}
(1)递归实现:
void Quick(int *arr,int start,int end)
{
int par = Partion(arr,start,end); //一次找基准
if(par > start+1)
{
Quick(arr,start,par - 1);
}
if(par < end - 1)
{
Quick(arr,par+1,end);
}
}
void Quick_Sort(int *arr,int len) //len为数组的长度
{
Quick(arr,0,len-1);
}
(2)非递归实现:
进行完一次排序后,将左右子序列的第一个和最后一个元素放在数组中,然后读取元素,重新进行排序。(如果子序列为单个数时,说明自身有序,不进行此过程)
一次入栈过程:
void Quick_Sort(int *arr,int len)
{
int tmpSize = (int)log((double)len)/log((double)2);
int *stack = (int *)malloc(sizeof(int)*tmpSize*2); //malloc开辟动态空间
assert(stack != NULL); //断言
int top = 0; //数组的下标
int low = 0;
int high = len - 1;
int par = Partion(arr,low,high); //第一次找基准
if(par > low + 1)
{
stack[top++] = low;
stack[top++] = par - 1;
}
if(par < high-1)
{
stack[top++] = par + 1;
stack[top++] = high;
}
while(top > 0) //栈不为空
{
high = stack[--top];
low = stack[--top];
par = Partion(arr,low,high);
if(par > low+1)
{
stack[top++] = low;
stack[top++] = par - 1;
}
if(par < high-1)
{
stack[top++] = par + 1;
stack[top++] = high;
}
}
free(stack); //释放空间
stack = NULL;
}
上述代码中,取基准的过程,实际上用到的是第一种取基准的方法,下面说明剩下两种方法
(1)随机选取基准法:
void Swap(int *arr,int low,int high)
{
int temp;
temp = arr[low];
arr[low] = arr[high];
arr[high] = temp;
}
void Rand(int *arr,int low int high)
{
srand((unsigned int)time(NULL)); //随机种子
Swap(arr,low,rand() % (high-low)+low); //取low ~ high之间的任意数字和low发生交换
}
(2)三分取中法
void Mid_of_Three(int *arr,int low,int high)
{
int mid = (high - low)/2+low ;
if(arr[mid] > arr[low]) //arr[mid] <= arr[low]
{
Swap(arr,mid,low);
}
if(arr[low] > arr[high]) //arr[low] <= arr[high]
{
Swap(arr,low,high);
}
if(arr[mid] > arr[high]) //arr[mid] <= arr[high]
{
Swap(arr,mid,high);
}
}
关于快速排序还有两种优化方式,下面进行介绍。
一、如果待排序序列,在排序过程当中数据量变很小时,用直接插入排序
void Insert_sort(int *arr,int low,int high)
{
for(int i = low+1;i <= high;i++)
{
int temp = arr[i];
int j = 0;
for(j = i-1;j >= low;j--)
{
if(arr[j] > temp)
{
arr[j+1] = arr[j];
}
else
{
break;
}
}
arr[j+1] = temp;
}
}
void Quick(int *arr,int low,int high)
{
if((high - low)+1 < 100)
{
Insert_sort(arr,low,high);
return;
}
}
void Quick_Sort(int *arr,int len)
{
Quick(arr,0,len-1);
}
二、聚集相同元素基准法
void FocusNumPar(int *arr,int low,int par,int high,int *left,int *right)
{
if(low < high)
{
int parLeft = par-1;
for(int i = par-1;i >= low;i--)
{
if(arr[i] == arr[par])
{
if(i != parLeft)
{
Swap(arr,i,parLeft);
parLeft--;
}
else
{
parLeft--;
}
}
}
*left = parLeft;
int parRight = par+1;
for(int i = par+1;i <= high;i++)
{
if(arr[i] == arr[par])
{
if(i != parRight)
{
Swap(arr,i,parRight);
parRight++;
}
else
{
parRight++;
}
}
}
*right = parRight;
}
}
void Quick(int *arr, int low, int high)
{
int par = Partion(arr,low,high);
int left = 0;
int right = 0;
FocusNumPar(arr,low,par,high,&left,&right);
if(left >= low+1) //说明左边有两个数据以上
{
Quick(arr,low,left);
}
if(right <= high-1)
{
Quick(arr,right,high);
}
}
void Quick_Sort(int *arr,int len)
{
Quick(arr,0,len-1);
}
[===>关于快速排序的三种方法及两种优化,读者若未能深入理解,可点击此处(https://blog.csdn.net/insistgogo/article/details/7785038)
五、堆排序
<1>时间复杂度:O(nlog2n)
<2>空间复杂度:O(log2n)
<3>稳定性:不稳定排序
<4>基本思想:将待排序序列构造成一个大根堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
备注:大根堆是每个结点的值都大于或等于其左右孩子结点的值;小根堆是每个结点的值都小于或等于其左右孩子结点的值。
<5>如图所示:
而实际上序列的存储形式是一维数组:
如图所示:
例如:25、15、88、45、8、1、33、21、55、6
<6>具体步骤:
(1)第一次排序:
(2)第二次排序:
以此类推,可得最后一次排序情况为:
此时排为大根堆,接下来只需要将根节点和最后一个节点交换值,然后对根节点进行一次排序即可.
如图所示:
<7>代码实现:
void Adjust (int *arr,int start,int end)
{
int temp = arr[start];
for(int i = 2*start +1;i<= end;i = 2*i+1) //i是左孩子下标
{
//判断是否有右孩子
if(i < end && arr[i] < arr[i+1]) //说明有右孩子,左孩子小于右孩子
{
i++;
}
if(arr[i] > temp)
{
arr[start] = arr[i];
start = i;
}
else
{
break;
}
}
arr[start] = temp;
}
void Heap_Sort(int *arr,int len)
{
int i = 0;
for(i = (len-1-1)/2;i >= 0;i--)
{
Adjust(arr,i,len-1);
}
//此时已经是大根堆
for(i = 0;i < len-1;i++)
{
int temp = arr[0];
arr[0] = arr[len-1-i];
arr[len-1-i] = temp;
Adjust(arr,0,len-1-i-1);
}
}
备注:
父====>>子
若父为n,则其右孩子为2n+1;左孩子为2n+2。
子====>>父
若子为n,则其父为(n-1)/2;
六、shell排序<希尔排序>
<1>时间复杂度:O(n^2)
(1)最好:O(n)
(2)最坏:O(n^2)
<2>空间复杂度:O(1)
<3>稳定性:不稳定排序
<4>基本思想:shell排序实际上是一种直接插入排序推广,其先将一组数分成若干组;此处应该注意,分组的方式不能几个几个紧挨着分组,而是采用每次所分组数均为素数且最后一次分组为1的方法,采用分组的好处是,在每次排序完后都是将小的数尽量往前面赶,大的数尽量往后面赶,最后一次排序直接采用直接插入排序。运用到了直接插入排序越有序有快的特性。
例如:12、5、9、34、6、8、33、56、89、0、7、4、22、55、77
具体步骤:
<5>代码实现:
void Shell(int *arr,int len,int gap)
{
for(int i = gap;i < len;i++)
{
int temp = arr[i];
int j = 0;
for(j = i-gap;j >= 0;j-=gap)
{
if(arr[j] > temp)
{
arr[j+gap] = arr[j];
}
else
{
break;
}
}
arr[j+gap] = temp;
}
}
void Shell_Sort(int *arr,int len)
{
int drr[] = {5,3,1}; //drr[]为分的组数,这里5,3,1不固定,但必须相互为素数,且最后数为1
int lend = sizeof(drr)/sizeof(drr[0]);
for(int i = 0;i < lend;i++)
{
Shell(arr,len,drr[i]);
}
}
七、归并排序
<1>时间复杂度:O(nlog2n)
<2>空间复杂度:O(n)
<3>稳定性:稳定排序
<4>基本思想:先使每个子序列有序,再使子序列段间有序。若将两有个有序表合成一个有序表,成为二路归并。
例如:15、2、35、6、23、11、5
具体步骤:
备注:其中s1,s2,e1,e2 分别表示两个归并段的首尾位置,brr[]为辅助数组。
<5>代码实现:
void Merge(int *arr,int len,int gap)
{
int *brr = (int *)malloc(sizeof(int) * len);
assert(brr != NULL);
int i = 0; //brr的下标
int start1 = 0;
int end1 = start1+gap-1;
int start2 = end1+1;
int end2 = start2+gap-1 < len-1 ? start2+gap-1 : len-1; //当有两个归并段的时候
while(start2 < len)
{
//当两个归并段还没有比较完的时候
while(start1 <= end1 && start2<=end2)
{
if(arr[start1] <= arr[start2])
{
brr[i++] = arr[start1++];
}
else
{
brr[i++] = arr[start2++];
}
}
while(start1 <= end1) //如果是第二个归并段比较完了,则将第一个归并段中的数放在brr[]
{
brr[i++] = arr[start1++];
}
while(start2 <= end2) //如果是第一个归并段比较完了,则将第二个归并段中的数放在brr[]
{
brr[i++] = arr[start2++];
}
//找两个新的归并段
start1 = end2+1;
end1 = start1+gap-1;
start2 = end1+1;
end2 = start2+gap-1 < len-1?start2+gap-1:len-1;
}
while(start1 < len) //如果在最后不存在第二个归并段,则将第一个归并段中的数放在brr[]
{
brr[i++] = arr[start1++];
}
for(int i = 0;i < len;i++) //数值拷贝
{
arr[i] = brr[i];
}
}
void MergeSort(int *arr,int len)
{
for(int i = 1;i < len;i *= 2)
{
Merge(arr,len,i);
}
}