今天接着来说数据结构中的后四种排序算法~
五、冒泡排序
所谓冒泡排序就是从数组的第一个数开始,依次从前向后依次将相邻记录的关键字进行比较,若前面的大于后面的,将两者交换,从而使得关键字值小的记录向上"飘浮"(左移),关键字值大的记录,向下“堕落”(右移)。适用于数据基本有序的序列
举一个例子:{12,69,55,25,6,24}
依次在沉淀25,24,12,6
代码如下:
//冒泡排序 稳定
void BubbleSort(int* arr,int len) // O(n^2) O(1) 稳定
{
int tmp;
//bool flag; 优化
int i,j;
for(i = 0;i < len-1;i++)
{
//flag = false;
for(j = 0;j < len-1-i;j++) //不减一会越界 len-i i=0时,j能取到len-1
{
if(arr[j] > arr[j+1])
{
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
//flag = true;
}
}
//if(!flag) //可防止有序,做无用功
//{
// return;
//}
}
}
六、快速排序
快速排序适用于数组是随机排序的情况,当数组基本有序时,快排的作用还不如插入排序。其的过程为:
1.先从数列中选取一个数作为基准(通常为第一个);
但是如果想优化的话,可以采用随机取基准点,使用rand()函数的方法,或者三数取中,比较left、mid、right三个数,取中间数当做基准,或者采取非递归的方法。一会我主要给大家介绍递归和非递归的快排代码。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边;
3.再对左右区间重复第二步,直到各区间只有一个数
给出例子{42,15,63,5,48,36},过程如下:
接下来以基准42,将区间分为两块,右边都比42大,左边都比42小。采用以上的方式,左边以36为左基准,low指向36,high指向5;右边以48为左基准,low指向48,high指向63;
最后的结果为:5,15,36,42,48,63;
递归代码如下:
/*快速排序 如果有序会怎么样,不太好 不稳定*/
static int Partition(int *arr,int low,int high) //划分好的区间进行排序 时间复杂度O(n)
{
int tmp = arr[low]; //基准
while(low < high)
{
while((low < high)&&(arr[high] >= tmp)) //从high方找小数字前放
{
high--;
}
if(low == high)
{
break;
}
else
{
arr[low] = arr[high];
}
while((low < high) && (arr[low] <= tmp)) //从low方找大数字后放
{
low++;
}
if(low == high)
{
break;
}
else
{
arr[high] = arr[low];
}
}
arr[low] = tmp; //基准放的位置
return low;
}
static void Quick(int* arr,int low,int high) // O(log2 n) O(1)
{
int par = Partition(arr,low,high);
if(low+1 < par) //保证左边最少两个
{
Quick(arr,low,par-1);
}
if(par < high-1) //保证右边最少两个
{
Quick(arr,par+1,high);
}
}
void QuickSort(int* arr,int len) // 递归调用 总体是O(nlog2n) O(log2n)空间复杂度大 不稳定
{
Quick(arr,0,len-1);
}
非递归的代码采取的是栈的方式:
void QuickSort(int *arr,int len) //非递归 用栈实现 空间复杂度不是1 是O(log2 n)
{
int size = (int)(log10((double)len)/log10((double)2))+1;
int *stack = (int*)malloc(size*2*sizeof(int));
assert(stack != NULL);
int top = 0;//栈顶指针,当前可以存放数据的下标
int low = 0;
int high = len -1;
int par = Partition(arr,low,high);
if(low +1 < par)
{
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 = Partition(arr,low,high);
if(low +1 <par) //保证左边最少有两个数据
{
stack[top++] = low;
stack[top++] = par-1;
}
if(par < high-1) //保证右边最少有两个数据
{
stack[top++] = par+1;
stack[top++] = high;
}
}
free(stack);
}
七、归并排序
“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。利用归并的思想容易实现排序。基本都是适用。
假设初始化序列含有n个记录,则可堪称是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到2/n个长度为2或1的有序子序列;再两两归并,...如此重复,直到得到一个长度为n的有序序列为止;
归并排序是基于分治法的,
第一步:递归划分初始序列,直到每个元素为一个序列;
第二步:划分完成后,再进行回溯,对每个序列进行排序合并;
例如:给定一个序列{36,52,12,4,20,85},利用归并排序的过程如图所示;
代码如下:
//归并排序
/*二路归并 两个归并段low1 high1,low2 high2,low1与low2相比,
谁小谁先下来,下来的那个++,再low1和low2相比依此方法*/
void Merge(int arr[],int len,int gap)//gap归并段的长度 O(n)
{
int *brr=(int *)malloc(len*sizeof(int));
assert(brr!=NULL);
int i=0;//brr下标
int low1=0;//第一个归并段的起始下标,下标可取
int high1=low1+gap-1;//第一个归并段的结束下标,下标可取
int low2=high1+1;//第二个归并段的起始下标,下标可取
int high2=low2+gap-1<len-1 ? low2+gap-1 : len-1;//第二个归并段的结束下标,下标可取
while(low2<len)//判断有两个归并段
{
//两个归并段都有数据
while(low1<=high1 && low2<=high2)
{
if(arr[low1]<=arr[low2])
{
brr[i++]=arr[low1++];
}
else
{
brr[i++]=arr[low2++];
}
}
//一个归并段没有数据,另一个还有
while(low1<=high1)
{
brr[i++]=arr[low1++];
}
while(low2<=high2)
{
brr[i++]=arr[low2++];
}
low1=high2+1;
high1=low1+gap-1;
low2=high1+1;
high2=low2+gap-1<len-1 ? low2+gap-1 : len-1;
}
//不足两个归并段
while(low1<len)//high1有可能越界
{
brr[i++]=arr[low1++];
}
for(i=0;i<len;i++)//将有效数据从brr中放回到arr中
{
arr[i]=brr[i];
}
free(brr);
}
void MergeSort(int *arr,int len)//O(nlogn);O(n);稳定
{
for(int i=1;i<len;i*=2)//O(logn)
{
Merge(arr,len,i);
}
}
八、基数排序
这个排序是和前面所学的排序方法完全不同的一种排序。这个排序方法是一种借助多关键字排序的思想对单逻辑关键字子进行排序的方法;适用于n值很大并且关键字较小的序列。
桶排序的基本步骤是:
第一步:遍历整个序列,找到最大的关键字,并获取该关键字的位数;
第二步:建立”桶“这一结构,利用二维数组实现;需要表示该“桶”的编号,以及每个”桶“内的编号;
第三步:遍历数组,从第一个关键字开始,获取其个位数字,根据所获得的数字将该关键字放到与所获数字对应的“桶”编号内;
第四步:一次结束后,将“桶”内的关键字一次出”桶“;
第五步:对出桶的元素重复三四步,直到获得到最高位的元素并出桶完成;
注意:
桶排序在大多数情况下是要快于快速排序的;
桶排序是利用空间替换时间,需要的空间大,但时间复杂度低;
给出例子{73, 22, 93, 43, 55, 14, 28, 65, 39, 81}
最后的结果为:14,22,28,39,43,55,65,73,81,93
代码如下:
//基数排序
#define MAXSIZE 10
int FindMaxFinger(int arr[],int len)
{
int maxnum=arr[0];
for(int i=1;i<len;++i)
{
if(arr[i]>maxnum)
{
maxnum=arr[i];
}
}
int count=0;
while(maxnum!=0)
{
maxnum/=10;
count++;
}
return count;
}
int FindFinNumber(int num,int fin)//找某个数的第fin位
{
return num/(int)pow(10.0,fin)%10;//(pow(x,y)计算x的y次幂)
}
void Radix(int arr[],int len,int fin)//入桶顺序:排的是哪一位(fin)
{
int backet[10][MAXSIZE]={};
int finnum=0;
int num[10]={};
for(int i=0;i<len;++i)
{
finnum=FindFinNumber(arr[i],fin);
backet[finnum][num[finnum]]=arr[i];
num[finnum]++;
}
int aindex=0;
int bindex=0;
for(int i=0;i<10;++i)
{
bindex=0;
while(bindex!=num[i])
{
arr[aindex++]=backet[i][bindex++];
}
}
}
void RadixSort(int arr[],int len)
{
int max=FindMaxFinger(arr,len);//最大数的位数
for(int i=0;i<max;++i)
{
Radix(arr,len,i);
}
}
为大家呈上一张表,关于这八种排序的总结,来自网络图~
最后想告诉大家,其实每一种排序各有优缺点,大家可以根据自己的数据的不同进行选择。