数据结构-内部排序汇总

目录

1.直接插入排序

2.折半插入排序

3.希尔排序

4.冒泡排序

5. 快速排序

6.二路归并排序

7.简单选择排序

8.堆排序

9.基数排序


排序的定义:对任意连续有限序列内的元素进行重新排列,使得该序列按关键字非递减或非递增。

排序的稳定性:待排序序列中存在两个不同位置的元素x=y 且 x在y 前面 ,若排序之后,仍然x在y前面,则称该排序算法是稳定的。

九大排序性质一览表

注意:本文排序时所有数组下标均从1开始使用。所有叙述及代码以从小到大排序为例。


1.直接插入排序

【思路】

设 a[ 1...i-1 ] 有序,接下来将 a[i] 移动到 [ 1...i-1 ] 之间合适的位置,使得a_{x}\leq a_{i}<a_{y}(满足此处使得排序稳定),其中1\leq x<y\leq i-1

【步骤】

1. 初始a[1]单独有序,若a[2]<a[1],则将a[2]移动到a[1]前面,否则不动

2. a[ 1...i-1 ] 已有序,用变量temp暂存a[i],然后依次考虑i-1, i-2 ... x, y,后移一步,直到有a_{x}\leq a_{i}<a_{y}

3. 将temp填到y位置

【复杂度分析】

时间复杂度:最好情况(原序列已有序)为O(n),最坏情况(原序列倒序)为O(n^2)

空间复杂度:仅使用了常数个辅助变量,故O(1)

【代码】

//直接插入排序
void InsertSort(int a[],int n)
{
    for(int i=2;i<=n;i++)
    {
        int j,temp=a[i];   //暂存a[i]
        for(j=i-1;j>=1&&a[j]>temp;j--) //后移,考虑a[j]移到a[j+1]
            a[j+1]=a[j];
        a[j+1]=temp;       //填空
    }
}

2.折半插入排序

【思路】

与直接插入排序基本类似,仅增加了折半查找的优化。

设 a[ 1...i-1 ] 有序,则可使用折半查找法快速找到ai的合适位置,使得a_{x}\leq a_{i}<a_{y}(满足此处使得排序稳定),其中1\leq x<y\leq i-1

但是折半查找仅仅减少了比较次数,元素依然需要依次移动,所以在数据量不太大的情况下,算法效率与直接插入法相差不大。

【步骤】

1. 初始a[1]单独有序,若a[2]<a[1],则将a[2]移动到a[1]前面,否则不动

2. a[ 1...i-1 ] 已有序,用变量temp暂存a[i],然后依次考虑i-1, i-2 ... x, y,后移一步,直到有a_{x}\leq a_{i}<a_{y}

3. 将temp填到y位置

【复杂度分析】

时间复杂度:最好情况(原序列已有序)为O(n),最坏情况(原序列倒序)为O(n^2)

空间复杂度:仅使用了常数个辅助变量,故O(1)

【代码】

void BiInsertSort(int a[],int n)
{
    for(int i=2;i<=n;i++)
    {
        int L=1,R=i,mid,temp=a[i];
        while(L<R)     //按递增有序,找到大于a[i]的第一个位置
        {
            mid=(L+R)>>1;
            if(a[mid]>a[i])R=mid;
            else L=mid+1;
        } //查找完成,有a[R-1]<=a[i]<a[R],即a[i]要放在a[R]前面
        for(int j=i-1;j>=R;j--)
            a[j+1]=a[j];
        a[R]=temp;       //填空
    }
}

3.希尔排序

【思路】

又名 缩小增量排序。

设有严格递减的增量序列D=[d_{1},d_{2},...,d_{t}],其中t>d_{1}>d_{2}>...>d_{t}=1。然后对原序列进行 t 次排序操作。

对于第 i 次排序操作,所有相隔d_{i}项的元素为一组,各组内进行直接插入排序。例如,序列[1,2,3,4,5,6,7,8]d_{i}=2,则[1,3,5,7]为一组,[2,4,6,8]为一组。

该算法的效果很大程度上取决于增量序列的设定,希尔提出了较好的增量序列d_{i}=\left\{\begin{matrix} n/2 & , & i=1 \\ \left \lfloor d_{i-1}/2 \right \rfloor & , & i \geqslant 2 \end{matrix}\right.

【步骤】

1. 选取增量序列,使用上述希尔提出的增量序列。遍历增量序列

2. 对于第i次排序,增量为d_{i},依次对同组的元素进行插入排序

【复杂度分析】

时间复杂度:没有标准答案,当n在某个特定范围内,约为O(n^{1.3}),最坏情况为O(n^2)

空间复杂度:仅使用了常数个辅助变量,故O(1)

【代码】

void ShellSort(int a[],int n)
{
    for(int di=n/2;di>=1;di>>=1) //增量序列
        for(int i=di+1;i<=n;i++) //前di个为每一组的第一个,组内有序,故始于第二个
        {
            int j,temp=a[i];
            for(j=i-di;j>=1&&a[j]>temp;j-=di) //后移,考虑a[j]移到a[j+di]
                a[j+di]=a[j];
            a[j+di]=temp;       //填空
        }
}

4.冒泡排序

【思路】

顾名思义。对于乱序序列,首先将最大数一步一步移动到末尾(第一次冒泡);其次将次大数移到末尾的前一个(第二次冒泡);以此类推,共须n-1次冒泡。移动过程中,相邻元素大小逆序时交换。

【步骤】

1. 对于第i次冒泡,必然已有i-1大数排在末尾,故遍历前n-(i-1)个数,将最大值移到第n-(i-1)位置

2. 冒泡n-1次,序列便有序

【重要优化】

1. 设标志位。对于第i次冒泡,设标志位flag=false; 本次冒泡过程中只要发生过元素移动(即交换),则修改flag=true; 若本次冒泡结束后flag=false; 则说明序列已有序,算法可以结束。

2. 双向冒泡。既然上述是把大数移动到末尾,当然把小数移动到开头也是对的,故可双向同时进行,向后冒一次泡,顺便向左冒一次泡。但该优化对算法效率没有明显改善,我不喜欢,下面代码中没有使用。

【复杂度分析】

时间复杂度:最好情况(原序列有序)为O(n),最坏情况(原序列逆序)为O(n^2)

空间复杂度:仅使用了常数个辅助变量,故O(1)

【代码】

void BubbleSort(int a[],int n)
{
    for(int i=1;i<n;i++)
    {
        for(int j=1;j<n-(i-1);j++)
        {
            if(a[j]>a[j+1])
                a[j]^=a[j+1]^=a[j]^=a[j+1]; //位运算交换a[j]与a[j+1],仅适用于整型
        }
    }
}

5. 快速排序

【思路】

也许快速排序在于快吧。快排采用分治思想。

对于序列A[ L,...., R ],不妨将其一分为二,满足 左半边所有值 < 右半边组所有值,则左右两边之间是有序的;进而如果左半边有序,右半边也有序,那么整个序列就有序了。怎样使左右两边各自有序呢? 让左右两边各自再去一分为二。最终,总会被分到只有一个元素,而一个元素必然是有序的,再往回想...

【步骤】

1. 对数组 a[ L, R ] 内进行排序,随机选一个中分值a[mid]。注:严蔚敏奶奶的代码总以第一个数为中分值。

2. 根据中分值,将小于它的数往左移,大于它的数往右移,最后将a[mid]放在中间,则左边任意值<=a[mid]<=右边任意值

3. 将左右两边分别进行步骤1

4. 若L=R,则表示单个元素,有序,不再执行步骤1。

【复杂度分析】

时间复杂度:最好情况(原序列随机乱序)为O(n*log_{2} n),最坏情况(中分值总选最小或最大值)为O(n^2)

空间复杂度:因递归,平均情况下O(log_{2} n),最坏情况下O(n)

【代码1-递归(参考严蔚敏奶奶的代码)】

void QuickSort(int a[],int L,int R)
{
    if(L<R)
    {
        int temp=a[L],low=L,high=R; //取第一个数为中分值
        while(low<high)
        {
            while(low<high&&a[high]>=temp)--high;
            a[low]=a[high];
            while(low<high&&a[low]<=temp)++low;
            a[high]=a[low];
        }
        a[low]=temp; //中分值;下面递归就没有必要放进两边了
        QuickSort(a,L,low-1);
        QuickSort(a,low+1,R);
    }
}

【代码2-非递归(借助队列,模拟二叉树层次遍历)】

void QuickSortQueue(int a[],int n)
{
    int queue[n*4],front=0,rear=0; //模拟队列
    queue[rear++]=1; queue[rear++]=n; //入队两个数 区间端点
    while(front<rear)
    {
        int L=queue[front++],R=queue[front++]; //取端点
        int low=L,high=R,temp=a[low];          //取第一个数为中分值
        while(low<high)
        {
            while(low<high&&a[high]>=temp)--high;
            a[low]=a[high];
            while(low<high&&a[low]<=temp)++low;
            a[high]=a[low];
        }
        a[low]=temp;
        if(L<low){queue[rear++]=L;queue[rear++]=low-1;} //子端点入队
        if(low<R){queue[rear++]=low+1;queue[rear++]=R;}
    }
}

【代码3-非递归(借助栈,模拟二叉树先序遍历)】

void QuickSortStack(int a[],int n)
{
    int stack[n*4],top=0;     //模拟栈
    stack[top++]=1; stack[top++]=n; //入栈两个数 区间端点
    while(top>0)
    {
        int R=stack[--top], L=stack[--top]; //先出栈的是R
        int low=L,high=R,temp=a[low];
        while(low<high)
        {
            while(low<high&&a[high]>=temp)--high;
            a[low]=a[high];
            while(low<high&&a[low]<=temp)++low;
            a[high]=a[low];
        }
        a[low]=temp;
        if(low<R){stack[top++]=low+1; stack[top++]=R;}
        if(L<low){stack[top++]=L; stack[top++]=low-1;}
    }
}

【代码4-单链表的快排】

#include<stdio.h>
#include<stdlib.h>
typedef struct LNode{
	int data;
	struct LNode *next;
}LNode,*LinkList;
 
//带有头结点的单链表快速排序算法 
void LQSort(LinkList h,LinkList end){
	//h所指结点不参与排序,h后继点作为分界结点 
	//end结点不参与排序,作为结束条件 
	//相当于开区间(h,end)排序 
	if(h->next==end)return;
	LinkList pre=h, now=h->next;
	while(now!=end){
		if(now->data < h->next->data)
		{
			pre->next = now->next;
			now->next = h->next;
			h->next = now;
			//上三句使用头插法将结点now转移到左边 
			now = pre->next;
		}else{ //now本身就在右边,无需移动 
			pre = now;
			now = now->next;	
		}
	}
	LQSort(h,h->next);
	LQSort(h->next,end);
}
 
int main()
{
	LinkList head = (LinkList)malloc(sizeof(LNode));
	head->next = NULL;
	
	int num;
	printf("请输入若干个数字,输入0结束:\n");
	scanf("%d",&num);
	while(num!=0){
		LinkList p = (LinkList)malloc(sizeof(LNode));
		p->data = num;
		p->next = head->next;
		head->next = p;
		scanf("%d",&num);
	}
	
	for(LinkList p=head->next;p!=NULL;p=p->next){
		printf("%3d ",p->data);
	}
	printf("\n\n");
	
	LQSort(head,NULL); //排序 
	
	for(LinkList p=head->next;p!=NULL;p=p->next){
		printf("%3d ",p->data);
	}
	printf("\n\n");
}

6.二路归并排序

【思路】

归并算法也是分治思想,但也可以说是快排的逆向思维。

若a[ L...M ]和a[ M+1...R ]分别有序,则可通过循环将两序列合并为有序,时间复杂度为O(n)。

如何将两个有序序列合并为一个有序序列?请查看文末附录

【步骤】

1. 初始状态,每个单个元素各自有序

2. 将两个元素合并为有序,作为一组

3. 将两组合并为一组,合并之后有序。以此类推,直到整个序列有序

【复杂度分析】

时间复杂度:O(n*log_{2} n)

空间复杂度:合并过程使用了辅助数组,故O(n)

【代码-递归】

int sup[100];
void MergeSort(int a[],int L,int R)
{
    if(L>=R)return;
    int M=(L+R)/2;
    MergeSort(a,L,M);
    MergeSort(a,M+1,R);
    int top=0,i=L,j=M+1;
    while(i<=M&&j<=R)
    {
        while(i<=M&&a[i]<=a[j])sup[top++]=a[i++];
        while(j<=R&&a[j]<=a[i])sup[top++]=a[j++];
    }
    while(i<=M)sup[top++]=a[i++];
    while(j<=R)sup[top++]=a[j++];
    for(i=L;i<=R;i++)a[i]=sup[i-L];  //复制回原数组a
}

【代码-非递归】

int sup[100];
void MergeSort2(int a[],int n)
{
    for(int k=1;k<n;k<<=1)
    {
        for(int s=1;s<=n;s+=k*2)
        {
            if(n<s+k)break; //第二段空
            int i=s,j=s+k,R1=s+k-1,R2=min(s+2*k-1,n),top=0;
            while(i<=R1&&j<=R2)
            {
                while(i<=R1&&a[i]<=a[j])sup[top++]=a[i++];
                while(j<=R2&&a[j]<=a[i])sup[top++]=a[j++];
            }
            while(i<=R1)sup[top++]=a[i++];
            while(j<=R2)sup[top++]=a[j++];
            for(i=s;i<=R2;i++)a[i]=sup[i-s];  //复制回原数组a
        }
    }
}

7.简单选择排序

【思路】

设 a[ 1...i-1 ] 有序且为序列a最小的i-1个数,接下来从i~n中选出最小数放在i位置,则a[ 1...i ]有序。

换个说法就是:依次给位置 i 找到其排序后应该存放的元素

【步骤】

1. 初始状态乱序,找到最小数放a[1]位置。

2. 从剩下的数中找到最小值放在第2位置;以此类推,直到n个位置都放上正确的数

【复杂度分析】

时间复杂度:最好情况(原序列有序)为O(0),最坏情况(逆序)为O(n^2)

空间复杂度:O(1)

【代码】

void SelectSort(int a[],int n)
{
    for(int i=1;i<n;i++)
    {
        int k=i;  //记下最小值下标
        for(int j=i+1;j<=n;j++)
            if(a[j]<a[k])k=j;
        if(k!=i) a[i]^=a[k]^=a[i]^=a[k]; //交换a[j]与a[k]
    }
}

8.堆排序

【思路】

堆:将待排序数组a看作完全二叉树的顺序存储结构,即 结点 i 的左孩子是 i*2,右孩子是 i*2+1,且任意结点的数值总是大于任意孩子的数值(若有孩子)

此时,堆顶a[1]必为最大值,取出a[1],重新调整堆,再取堆顶,得次大值。以此类推

【步骤】

1. 将原序列看作完全二叉树的顺序存储,调整为大顶堆

2. 将堆顶a[1]与a[n]交换,删除第n个结点,重新调整为大顶堆。

3. 直到大顶堆仅剩一个结点,数组a中元素便有序。

【复杂度分析】

时间复杂度:O(n*log_{2} n)

空间复杂度:O(1)

【代码】

void HeapAdjust(int a[],int p,int maxVex) //堆的结点p向下调整
{
    int temp=a[p];
    while(p*2<=maxVex)    //有孩子则尝试调整
    {
        int kid=p*2;           //先假设左孩子大于父结点
        if(kid<maxVex&&a[kid+1]>a[kid])kid=kid+1; //右孩子更大
        if(temp>=a[kid])break;   //p树已为大顶堆
        a[p]=a[kid];
        p=kid;
    }
    a[p]=temp; //填空
}
void HeapSort(int a[],int n)
{
    for(int i=n/2;i>=1;i--)//调整完全二叉树为堆
        HeapAdjust(a,i,n);
    for(int i=n;i>1;i--)
    {
        a[i]^=a[1]^=a[i]^=a[1]; //交换
        HeapAdjust(a,1,i-1);
    }
}

9.基数排序

【思路】

长度为n的待排序序列,每个元素x有d个关键字。举个例子,若元素是数字123,按十进制位分解可得3个关键字1,2,3。

先考虑优先级最低的关键字,进行一次稳定的排序,继而考虑优先级次低的关键字,类推

【复杂度分析】

时间复杂度:O(nd),d是元素关键字个数.

空间复杂度:O(n+r),r是关键字种类,例如按十进制分解,则r=10

【代码】

//基数排序 按十进制位分解
void BaseSort(int a[],int n)
{
    int b[n+1],u[10],v[10],maxNum=0;
    for(int i=1;i<=n;i++)maxNum=max(maxNum,a[i]);
    for(int ten=1;ten<=maxNum;ten*=10)
    {
        for(int i=0;i<10;i++)u[i]=v[i]=0;
        for(int i=1;i<=n;i++)u[a[i]/ten%10]++;
        for(int i=1;i<10;i++)u[i]+=u[i-1];
        for(int i=1;i<=n;i++)b[i]=a[i];
        for(int i=1;i<=n;i++)
        {
            int x=b[i]/ten%10;
            if(x==0)a[v[x]+1]=b[i];
            else a[ u[x-1]+v[x]+1 ]=b[i];
            v[x]++;
        }
    }
}

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
十大经典排序算法是指在计算机科学中常用的排序算法,它们分别是: 1. 冒泡排序(Bubble Sort):重复地比较相邻的两个元素,将较大的元素逐渐向右移动。 2. 选择排序(Selection Sort):每次从未排序的部分选择最小(或最大)的元素,并放在已排序的部分的末尾。 3. 插入排序(Insertion Sort):将未排序的元素逐个插入到已排序的部分中的正确位置。 4. 希尔排序(Shell Sort):将待排序的数组按照一定步长进行分组,对每组进行插入排序,逐渐减小步长。 5. 归并排序(Merge Sort):将待排序的数组递归地分成两半,对每一半进行排序,然后合并两个有序数组。 6. 快速排序(Quick Sort):选择一个基准元素,将数组划分为两部分,左边部分都小于基准,右边部分都大于基准,递归地对两部分进行排序。 7. 堆排序(Heap Sort):将待排序的数组构建成一个最大堆(或最小堆),然后依次取出堆顶元素并调整堆结构。 8. 计数排序(Counting Sort):统计数组中每个元素出现的次数,然后根据统计结果对元素进行排序。 9. 桶排序(Bucket Sort):将待排序的数组划分为多个桶,对每个桶中的元素进行排序,最后将桶中的元素按顺序合并。 10. 基数排序(Radix Sort):按照元素的位数,将待排序的数组从低位到高位进行排序。 以上是十大经典排序算法,每种算法都有其适用的场景和性能特点,选择合适的排序算法可以提高程序的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪的期许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值