1216排序

排序

内部排序和外部排序

定义:

内部排序:在内存中排序;

外部排序:有辅助存储的排序(有可能有内存)

时间效率:比较次数和移动次数

空间效率:占内存辅存空间的大小

稳定性:A和B的关键字相等,排序后A、B的先后次序保持不变,则称这种排序算法是稳定的。

规则:插入排序,交换排序,选择排序,归并排序

插入排序

基本思想

每步将一个待排序的对象,按其关键字大小,插入到前面已经排好序的数列中适当的位置

直接插入排序

顺序查找后进行插入排序

基本步骤:在R[1…i-1]中查找R[i]的插入位置;

void InsertSort(SQList &L)
{
	int i,j;
    for(i=2;i<=L.length;i++)
    {
        if(L.r[i].key<L.r[i-1].key)//确定需要排序的数,往前找
        {
            L.r[0]=L.[i];//哨兵
            L.r[i]=L.r[i-1];//开始后移
            for(j=i-2;L.r[0].key<L.r[j].key;--j)//往前找
            {
                L.r[j+1]=L.r[j];
            }
             L.[j+1]=L.r[0];
        }
    }
}

时间复杂度O(n^2),空间复杂度O(I)

最好的情况:每趟只需要比1次,总比较次数n-1

最坏的情况:第i趟比较i次

平均比较次数和移动次数为(n^2)/4

折半插入排序

void BInsertSort(SQList &L)
{
    for(i=0;i<=L.Length;i++)
    {
        L.r[0]=L.r[i];
        low=1;
        high=i-1;
        while(low<=high)
        {
            m=(low+high)/2;
            if(L.r[0].key<L.r[m].key)high=m-1;
            else low=m+1
        }
        for(j=i-1;j>=high+1;--j)L.r[j+1]=L.r[j];//元素往后移
        L.r[high+1]=L.[0]//赋值
    }
}

分析:减少了比较次数,但没有减少移动次数

​ 平均性能优于直接插入排序

时间复杂度:O(n^2);空间复杂度O(I)

希尔排序

基本思想:先将整个待排记录序列分割成若干个子序列(减少记录数量),分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。

子序列的构成不是简单地逐段分割,而是将相隔某个增量dk的记录组成一个子序列让增量dk逐趟缩短,直到dk=1

void ShellSort(SqList &L,int dt[],int t)
{
    //按增量序列dt[0,...t-1]对顺序表做Shell排序
    for(k=0;k<t;k++)
    {
        ShellInsert(L,dt[k]);
    }
}
void ShellInsert(SqList &L,int dk)
{
    for(i=1+dk;i<L.length;i++)
    {
        if(r[i].key<r[i-dk].key)
        {
            r[0]=r[i];//找到待排序数字
            for(j=i-dk;j>0&&(r[0].key<r[j].key);j=j-dk)//找到应该插入的位置
            {
                r[j+dk]=r[j];
            }
            r[j]=r[0];//插入排序
        }
    }
}

void shell_sort(const int start, const int end) {
	int increment = end - start + 1;	//初始化划分增量
	int temp{ 0 };
	do {	//每次减小增量,直到increment = 1
		increment = increment / 3 + 1;
		for (int i = start + increment; i <= end; ++i) {	//对每个划分进行直接插入排序
			if (numbers[i - increment] > numbers[i]) {
				temp = numbers[i];
				int j = i - increment;
				do {	//移动元素并寻找位置
					numbers[j + increment] = numbers[j];
					j -= increment;
				} while (j >= start && numbers[j] > temp);
				numbers[j + increment] = temp;	//插入元素
			}
		}
	} while (increment > 1);
}

分析:空间复杂度O(1),是一种不稳定的排序方式

交换排序

基本思想:两两比较,如果发生逆序则交换,直到所有记录都排好序为止

起泡排序

优点:理顺数据,挤出最大值,没有交换动作时结束函数

时间复杂度O(n^2)

void bubble_sort(SqList &L)
{
    int m,i,j,flag=1;RedType t;
    m=L.length-1;
    while((m>0)&&(flag==1))
    {
        flag=0;
        for(j=1;j<=m;j++)
        {
            if(L.r[j].key>L.r[j+1].key)
            {
                flag=1;
                t=L.r[j];
                L.r[j]=L.r[j+1];
                L.r[j+1]=t;
            }
        } 
        m--;
    }
}

快速排序

基本思想:任取一个元素为中心,所有比它大的元素前放,所有比它小的元素后放。对各子表重新选取中心元素并依据此规则进行调整

时间复杂度O(nlog2n),空间效率O(log2n)【递归部分的辅助空间】

最坏的情况:关键字基本排好序的情况下,时间复杂度为O(n^2)

结论:就平均计算时间来说,快速排序是目前学过的最好的排序方法。具有不稳定性

void main()
{
    QSort(L,1,L.length);
}
void QSort(SqList &L,int low,int high)
{
    if(low<high)
    {
        pivotloc=Partition(L,low,high);
        QSort(L,low,pivotloc-1);
        QSort(L,pivotloc+1,high);
    }
}
int Partition(SqList &L,int low,int high)
{
    L.r[0]=L.r[low];
    pivotkey=L.r[low].key;
    while(low<high)
    {
        while(low<high&&L.r[high].key>=pivotkey)--high;
        L.r[low]=L.r[high];
        while(low<high&&L.r[low].key<=pivotkey)++low;
        L.r[high]=L.r[low];
    }
    L.r[low]=L.r[0];
    return low;
}

选择排序

基本思想:每一趟在后面n-i+1个中选出关键码最小的对象,作为有序序列的第i个记录

简单选择排序

void  SelectSort(SqList &K)
{
    for(i=1;i<L.length;++i)
    {
        k=i;
        for(j=i+1;j<=L.length;j++)
        {
            if(L.r[j]<L.r[k].key)k=j;
        }
        if(k!=i)
            {
                t=L.r[k];
                L.r[k]=L.r[i];
                L.r[i]=t;
            }
    }
}

移动次数:最好情况——0;最坏情况——3(n-1)【每次赋值就算一次移动】

比较次数(n-i)连加

时间效率:O(n^2)

空间复杂度:O(1)

稳定

树形选择排序(锦标赛排序)

简单排序没有利用前一次的比较结果,最小的数求出后,应该从被他打败过的数挑选出次小的数

比较次数:[log2n]+1

堆排序(重要⭐)

定义:树状结构,上一层的数一直小于/大于它的孩子结点

基本思想:将无序序列建成一个堆;输出堆顶的最大(小)值;使剩余的n-1个元素又调整成一个堆,则可得到n个元素的次小值;重复执行,得到一个有序序列

步骤:从第n/2个元素起,至第一个元素止,进行反复筛选;【选出大的值作为根节点】

输出堆顶元素后,以堆中最后一个元素替代之;【扣除已经确定的数】

将根节点与左右子树根结点比较,并与最小者交换

重复直至叶子结点,得到新的堆

大根堆/小根堆

分析:时间效率:O(nlog2n)

空间效率:O(1)

稳定性:不稳定,适用于n较大的情况

void BuildHeap(SeqList R)
{
	//将初始文件R[1..n]构造为堆
	int i;
	for(i=n/2; i>0; i--)
	Heapify(R,i,n);						//将R[i..n]调整为大根堆
}

//2.========大根堆调整=====
void Heapify( SeqList R,int low,int high)
{
//有左孩子且左孩子数据大于双亲结点
if(low*2<=high&&R[low].key<R[low*2].key)
	{
		R[0]=R[low];
		R[low]=R[low*2];
		R[low*2]=R[0];
	}
有右孩子且右孩子数据大于双亲结点
	if(low*2+1<=high&&R[low].key<R[low*2+1].key)
	{
		R[0]=R[low];
		R[low]=R[low*2+1];
		R[low*2+1]=R[0];
	}
}

//3.========堆排序=====
void HeapSort(SeqList R)
{
	//对R[1..n]进行堆排序,不妨用R[0]做暂存单元
	int i;
	BuildHeap(R);						//将R[1..n]构造为初始大根堆
	for(i=n; i>1; i--)
	{	//对当前无序区R[1..i]进行堆排序,共做n-1趟。
		R[0]=R[1];
		R[1]=R[i];
		R[i]=R[0];						//将堆顶和堆中最后一个记录交换
		//将R[1..i-1]重新调整为堆,仅有R[1]可能违反堆性质。
		Heapify(R, 1, i-1);
	}
}

归并排序

定义:将两个或两个以上的有序表组合成一个新有序表

分析:时间效率O(nlog2n);空间效率:O(n);稳定性:稳定

void MergePass(SeqList R, int length)
{

	int m=n/length;//求总组数
	if(m*length!=n)m++;//除法是向下取整的,若非整除组数需+1
	for(int i=1;i<=m;i+=2)//二路归并
	{
		int a,b,k;//定义两组的判断位置和已排好序部分的下一个位置
		a=(i-1)*length+1;//a组的起始位置
		b=a+length;//b组的起始位置
		k=a;// 已排好序部分的下一个位置
		if (b>n)return;//不存在b组的情况,排序完毕
//确定b组的结束位置,调用一次归并的函数(a组的结束位置只有一种情况)
		if((i+1)*length<=n)
			MergeOne(R,a,b,i*length,(i+1)*length);
		else
			MergeOne(R,a,b,i*length,n);
	}
}
SeqList R1; //辅助空间
void MergeOne(SeqList R,int s1,int s2,int e1,int e2)//合并两组数据
{
	int i=s1,j=s2,p=s1;                //置初始值  
    while(i<=e1 && j<=e2)   //两子文件非空时取其小者输出到R1[p]上  
    {  
        R1[p++]=(R[i].key<=R[j].key)?R[i++]:R[j++];  
    }  
  
    while(i<=e1)      //若第1个子文件非空,则复制剩余记录到R1中  
    {  
        R1[p++]=R[i++];  
    }  
    while(j<=e2)     //若第2个子文件非空,则复制剩余记录到R1中  
    {  
        R1[p++]=R[j++];  
    }  
    for(p=s1;p<=e2;p++)  
    {  
        R[p]=R1[p];  //归并完成后将结果复制回去 
    }  
}
//2.========二路归并排序=====
void MergeSort(SeqList R)
{
	int length;
	for (length=1; length<n; length*=2)		//做[lgn]趟排序
		MergePass(R,length);					//有序长度≥n时终止
}


基数排序

n个记录,每个记录有d位关键字,关键字取值范围rd[如十进制为10]

时间效率O(d(n+rd));空间效率O(n+rd)

外部排序P165

外排总时间=产生初始归并段的时间+外存信息读写时间+内部归并所需时间

外存信息读写时间远大于排序时间,所以外排时间取决于读写外存的次数

假设待排记录系列含有m个初始归并段,外排采用k路归并,则归并趟数s=[logkm]

胜者树和败者树都是完全二叉树,是树形选择排序的一种变型。每个叶子结点相当于一个选手,每个中间结点相当于一场比赛,每一层相当于一轮比赛。

胜者树

胜者树的一个优点是,如果一个选手的值改变了,可以很容易地修改这棵胜者树。只需要沿着从该结点到根结点的路径修改这棵二叉树,而不必改变其他比赛的结果。

败者树

败者树是胜者树的一种变体。在败者树中,用父结点记录其左右子结点进行比赛的败者,而让胜者参加下一轮的比赛。败者树的根结点记录的是败者,需要加一个结点来记录整个比赛的胜利者。采用败者树可以简化重构的过程。

败者树简化了重构。败者树的重构只是与该结点的父结点的记录有关,而胜者树的重构还与该结点的兄弟结点有关。

每次从k个组中的首元素中选一个最小的数,加入到新组,这样每次都要比较k-1次,故算法复杂度为O((n-1)*(k-1)),而如果使用败者树,可以在O(logk)的复杂度下得到最小的数,算法复杂度将为O((n-1)*logk), 对于外部排序这种数据量超大的排序来说,这是一个不小的提高。

置换-选择排序的操作

长度分析:内存可读入m个记录,合并段的平均长度为2m

败者树实现置换-选择排序

typedef struct
{
    RcdType rec;//记录
    KeyType key;//关键字
    int rnum;//所属归并段的段号
}RcdNode,WorkArea[w];
void Replace_Selection(LoserTree &ls,WorkArea &wa,FILE *fi,FILE *fo)
{
    Construct_Loser}

最佳归并树

http://data.biancheng.net/view/79.html

排序算法比较

快速比较是基于比较的内部排序中平均性能最好的

基数排序时间复杂度最低,但对关键字结构有要求【知道各级关键字的主次关系和取值范围】

习题

1.快速排序在下列( )情况下最易发挥其长处。

A.被排序的数据中含有多个相同排序码

B.被排序的数据已基本有序

C.被排序的数据完全无序

D.被排序的数据中的最大值和最小值相差悬殊

答案:C

解释:B选项是快速排序的最坏情况。

2.不稳定排序有希尔排序、快速排序、堆排序;稳定排序有直接插入排序、折半插入排序、冒泡排序、归并排序、基数排序、简单选择排序。

3.下列排序算法中,( )不能保证每趟排序至少能将一个元素放到其最终的位置上。

A.希尔排序 B.快速排序 C.冒泡排序 D.堆排序

答案:A

解释:快速排序的每趟排序能将作为枢轴的元素放到最终位置;冒泡排序的每趟排序能将最大或最小的元素放到最终位置;堆排序的每趟排序能将最大或最小的元素放到最终位置。

3.给出如下关键字序列{321,156,57,46,28,7,331,33,34,63},试按链式基数排序方法,列出每一趟分配和收集的过程。

答案:

按最低位优先法 →321→156→57→46→28→7→331→33→34→63

  分配 [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]

           321     33   34     156  57  28

           331     63           46   7

收集 →321→331→33→63→34→156→46→57→7→28
借助于快速排序的算法思想,在一组无序的记录中查找给定关键字值等于key的记录。设此组记录存放于数组r[l…n]中。若查找成功,则输出该记录在r数组中的位置及其值,否则显示“not find”信息。请简要说明算法思想并编写算法。

[题目分析]把待查记录看作枢轴,先由后向前依次比较,若小于枢轴,则从前向后,直到查找成功返回其位置或失败返回0为止。

[算法描述]

int index (RecType R[],int l,h,datatype key)

{int i=l,j=h;

 while (i<j)

    { while (i<=j && R[j].key>key) j--;

      if (R[j].key==key) return  j;

      while (i<=j && R[i].key<key) i++;

      if (R[i].key==key) return  i;

     }

    cout<<“Not find”; return  0;

 }//index
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值