数据结构与算法 排序

一.概述
1.基于比较的排序:

以下排序都是"基于比较的排序"(Comparison-Based Sorting),即除赋值外,对输入的数据进行的操作只能是大于/小于的比较(>/<)
另外,将假定传入到排序函数中的数据都是合法的,并用1个数组接收;同时接收1int,表示共传入几个数据
最后,注意位置都是从0开始的.所以在位置i处的数据实际上是第i+1个数据

2.简单排序算法的下界:

假设序列中不存在重复元素且所有排列都是等可能的.在这些假设下,有如下定理:

定理7.1:NNN个互异数构成的数组的平均逆序数是N(N−1)4\frac{N(N-1)}{4}4N(N1)
在这里插入图片描述

定理7.2:通过交换相邻元素进行排序的任何算法平均需要Ω(N2)Ω(N^2)Ω(N2)的时间
在这里插入图片描述

二.插入排序
1.基本思想:

"插入排序"(Insertion Sort)通过N-1次插入来实现排序:第i次(1≤i≤N-1)插入将第i+1个的数据插入到前i+1个数据中合适的位置处(见下表)
初始3 4 1 4 3 1 4 0 11 42移动的元素
p=1后3 4 1 4 3 1 4 0 11 42×
p=2后1 3 4 4 3 1 4 0 11 42A[2]
p=3后1 3 4 4 3 1 4 0 11 42×
p=4后1 3 3 4 4 1 4 0 11 42A[4]
p=5后1 1 3 3 4 4 4 0 11 42A[5]
p=6后0 1 1 3 3 4 4 4 11 42A[6]
p=7后0 1 1 3 3 4 4 4 11 42×
p=8后0 1 1 3 3 4 4 4 11 42×

2.代码实现:

#include <stdio.h>
typedef int ElementType;

void InsertionSort(ElementType A[], int N) {
	int j, P;
	ElementType Tmp;
	for (P = 1; P < N; P++) {
		Tmp = A[P];
		for (j = P; j > 0 && A[j - 1] > Tmp; j--) {
			A[j] = A[j - 1];
		}
		A[j] = Tmp;
	}
}

int main() {
	ElementType A[10] = { 3,4,1,4,3,1,4,0,11,42 };
	InsertionSort(A, 10);
	int i;
	for (i = 0; i < 10; i++) {
		printf("%d ", A[i]);
	}
}
//结果:
0 1 1 3 3 4 4 4 11 42

3.算法分析:

最坏和平均时间复杂度均为O(N^2),最好时间复杂度(要求输入的A是已排序的)O(N).空间复杂度为O(1)

三.希尔排序
1.基本思想:

"希尔排序"(Shell Sort)通过比较相隔一定距离的元素来工作,且每轮排序中比较的距离随着算法的进行而缩小,直到只比较相邻元素的最后1轮为
止,因此希尔排序也叫"缩小增量排序"(Diminishing Increment Sort)
希尔排序使用1"增量序列"(Increment Sequence)h1,h2...ht来确定每轮比较的距离.理论上,只要满足h1=1,任何增量序列都是可行的,不过
有些更好.使用增量hk的1轮排序后,对∀i有A[i]≤A[i+hk],此时称序列是"hk-排序的"(hk-sorted)(见下表).并且希尔排序的1个重要性质是1个
hk-排序的序列将保持其hk-排序性.事实上,如果并非如此,该算法也就没有什么意义了,因为各次排序的结果会被之后的各次排序打乱
初始81 94 11 96 12 35 17 95 28 58 41 75 15
在5-排序后35 17 11 28 12 41 75 15 96 58 81 94 95
在3-排序后28 12 11 35 15 41 58 17 94 75 81 96 95
在1-排序后11 12 15 17 28 35 41 58 75 81 94 95 96
hk-排序的一般做法是:对hk,hk+1...N-1中的每个位置i,把其上的元素放到i,i-hk,i-2*hk...中的正确位置上.也就是说,1轮hk-排序的作用就
是对hk个独立的子数组执行1次插入排序

2.代码实现:

#include <stdio.h>
typedef int ElementType;

void ShellSort(ElementType A[], int N) {
	int i, j, Increment;
	ElementType Tmp;
	for (Increment = N / 2; Increment > 0; Increment /= 2) {//这里使用的是希尔增量序列
		for (i = Increment; i < N; i++) {
			Tmp = A[i];
			for (j = i; j >= Increment; j -= Increment) {
				if (Tmp < A[j - Increment]) {
					A[j] = A[j - Increment];
				}
				else {
					break;
				}
			}
			A[j] = Tmp;
		}
	}
}

int main() {
	ElementType A[10] = { 3,4,1,4,3,1,4,0,11,42 };
	ShellSort(A, 10);
	int i;
	for (i = 0; i < 10; i++) {
		printf("%d ", A[i]);
	}
}
//结果:
0 1 1 3 3 4 4 4 11 42

3.算法分析:

希尔排序的运行时间依赖于增量序列的选择.下面证明在2个特定增量序列下最坏情况的精确边界

增量序列的1种流行(但不好)的选择是使用Shell建议的增量序列:ht=⌊N2⌋,hk=⌊hk+12⌋h_t=⌊\frac{N}{2}⌋,h_k=⌊\frac{h_{k+1}}{2}⌋ht=2N,hk=2hk+1
定理7.3:使用希尔增量序列时希尔排序的最坏情形运行时间为Θ(N2)Θ(N^2)Θ(N2)
在这里插入图片描述

Hibbard提出了另1个更好的增量序列:1,3,7...2k−11,3,7...2^k-11,3,7...2k1定理7.4:使用Hibbard增量序列的希尔排序的最坏情形运行时间为Θ(N32)Θ(N^\frac{3}{2})Θ(N23)
在这里插入图片描述

其他增量序列:
在这里插入图片描述

四.堆排序
1.基本思想:

"堆排序"(Heapsort)使用优先队列进行排序:首先建立1个包含所有要排序的元素的二叉堆,花费O(N)的时间;然后执行N次DeleteMaxPriority(),并
用1个数组按顺序接收被删除的元素,每次花费O(log N)的时间;再将数组拷贝回原数组,花费O(N)的时间.这样就得到了这N个元素从小到大排序后的数
组.每次删除花费O(log N)的时间,因此总的运行时间是O(N*log N)
该算法的问题在于使用了1个额外的数组,因此所需的内存资源增加了1.1种解决方法是在每次DeleteMaxPriority(),让堆缩小1个元素的大小;并
将刚删去的元素放在节约出的空间上.使用此策略时,为了最终得到递增的序列,应使用最大堆

2.实现:

在下述实现中,由于速度的原因没有使用实际的max二叉堆,而是使用了1个数组来模拟二叉堆

在这里插入图片描述

#define LeftChild(i) (2*(i)+1)
typedef int ElementType;

void PercDown(ElementType A[],int i,int N) {
    int Child;
    ElementType Tmp;
    for (Tmp = A[i];LeftChild(i) < N;i = Child) {
        Child = LeftChild(i);
        if (Child != N - 1 && A[Child + 1] > A[Child]) {
            Child++;
        }
        if (Tmp < A[Child]) {
            A[i] = A[Child];
        } else {
            break;
        }
    }
    A[i] = Tmp;
}

void Swap(ElementType * a,ElementType * b) {
    ElementType Temp = *a;
    *a = *b;
    *b = Temp;
}

void HeapSort(ElementType A[],int N) {
    int i;
    for (2 = N / 2;i >= 0;i--) {//建立max二叉堆
        PercDown(A,i,N);
    }
    for (i = N - 1;i > 0;i--) {
        Swap(&A[0],&A[i]);//DeleteMaxPriority
        PercDown(A,O,i);
    }
}

3.算法分析:
在这里插入图片描述
在这里插入图片描述

定理7.5:对NNN个互异项的随机排列进行堆排序,所用的平均比较次数为2Nlog⁡N−O(Nlog⁡log⁡N)2N\log{N}-O(N\log{\log{N}})2NlogNO(NloglogN)
在这里插入图片描述

五.归并排序
1.基本思想:

"归并排序"(Merge Sort)1种递归算法,也是1种分治算法.其基本操作是合并2个已排序的表.合并算法使用了2个输入数组A,B/1个输出数组C/3个计
数器Aptr,Bptr,Cptr:3个计数器均从各自对应的数组的首元素开始;不断将A[Aptr]和B[Bptr]中的较小者拷贝到C[Cptr];每次拷贝后都将使用到的
2个计数器+1.归并排序的原理为:将全部数据分为2部分,递归地对2部分分别进行归并排序,然后使用上述方法合并这2部分

在这里插入图片描述
2.实现:

#include <stdio.h>
#include <malloc.h>
typedef int ElementType;

void Merge(ElementType A[],ElementType TmpArray[],int Lpos,int Rpos,int RightEnd) {
    int i,LeftEnd = Rpos - 1,NumElements = RightEnd - Lpos + 1,TmpPos = Lpos;
    while (Lpos <= LeftEnd && Rpos <= RightEnd) {
        if (A[Lpos] <= A[Rpos]) {
            TmpArray[TmpPos++] = A[Lpos++];
        } else {
            TmpArray[TmpPos++] = A[Rpos++];
        }
    }
    while (Lpos <= LeftEnd) {
        TmpArray[TmpPos++] = A[Lpos++];
    }
    while (Rpos <= RightEnd) {
        TmpArray[TmpPos++] = A[Rpos++];
    }
    for (i = 0;i < NumElements;i++,RightEnd--) {
        A[RightEnd] = TmpArray[RightEnd];
    }
}

void MSort(ElementType A[],ElementType TmpArray[],int Left,int Right) {
    int Center;
    if (Left < Right) {
        Center = (Left + Right) / 2;
        MSort(A,TmpArray,Left,Center);
        MSort(A,TmpArray,Center + 1,Right);
        Merge(A,TmpArray,Left,Center + 1,Right);
    }
}

void MergeSort(ElementType A[],int N) {
    ElementType * TmpArray;
    TmpArray = malloc(N * sizeof(ElementType));
    if (TmpArray != NULL) {
        MSort(A,TmpArray,0,N-1);
        free(TmpArray);
    }
}

int main(void) {
	ElementType A[10] = {3,4,1,9,3,4,2,22,11,7};
	MergeSort(A,10);
	int i;
	for (i = 0;i < 10;i++) {
		printf("%d ",*(A + i));
	}
	return 0;
}
//结果:
1 2 3 3 4 4 7 9 11 22

3.算法分析:

合并2个已排序的表的时间是线性的:因为除最后1次比较外,每次比较后只将1个元素放入CCC而最后1次比较后至少将2个元素放入CCC,因此最多进行N-1次比较,其中N为2个表中的元素总数

假设N=2kN=2^kN=2k,从而可将全部元素分为均包含偶数个元素的2部分.若N=1N=1N=1,则用时为常数;否则,用时为完成2个大小为N/2N/2N/2的归并排序的用时加合并2个已排序的表的用时.也就是说:T(1)=1T(N)=2T(N2)+NT(1)=1\\T(N)=2T(\frac{N}{2})+NT(1)=1T(N)=2T(2N)+N
求解该递归关系的方法有多种:
①T(N)=2T(N2)+N⇒T(N)N=T(N2)N2+1⇒{ T(N)N=T(N2)N2+1T(N2)N2=T(N4)N4+1   ...   T(2)2=T(1)1+1⇒T(N)N=T(1)1+log⁡N⇒T(N)=Nlog⁡N+N=O(Nlog⁡N)②{T(N)=2T(N2)+NT(N2)=2T(N4)+N2⇒{T(N)=4T(N4)+2NT(N4)=2T(N8)+N4 ⇒... ⇒T(N)=2kT(N2k)+kN =NT(1)+Nlog⁡N =Nlog⁡N+N =O(Nlog⁡N)①T(N)=2T(\frac{N}{2})+N⇒\frac{T(N)}{N}=\frac{T(\frac{N}{2})}{\frac{N}{2}}+1\\\qquad\qquad\qquad\qquad\qquad⇒\begin{cases}\:\frac{T(N)}{N}=\frac{T(\frac{N}{2})}{\frac{N}{2}}+1\\\frac{T(\frac{N}{2})}{\frac{N}{2}}=\frac{T(\frac{N}{4})}{\frac{N}{4}}+1\\\qquad\:\:\,...\\\:\:\,\frac{T(2)}{2}=\frac{T(1)}{1}+1\end{cases}\\\qquad\qquad\qquad\qquad\qquad⇒\frac{T(N)}{N}=\frac{T(1)}{1}+\log{N}\\\qquad\qquad\qquad\qquad\qquad⇒T(N)=N\log{N}+N\\\qquad\qquad\qquad\qquad\qquad\qquad\qquad=O(N\log{N})\\②\begin{cases}T(N)=2T(\frac{N}{2})+N\\T(\frac{N}{2})=2T(\frac{N}{4})+\frac{N}{2}\end{cases}⇒\begin{cases}T(N)=4T(\frac{N}{4})+2N\\T(\frac{N}{4})=2T(\frac{N}{8})+\frac{N}{4}\end{cases}\\\qquad\qquad\qquad\qquad\qquad\quad\:⇒...\\\qquad\qquad\qquad\qquad\qquad\quad\:⇒T(N)=2^kT(\frac{N}{2^k})+kN\\\qquad\qquad\qquad\qquad\qquad\qquad\qquad\quad\:=NT(1)+N\log{N}\\\qquad\qquad\qquad\qquad\qquad\qquad\qquad\quad\:=N\log{N}+N\\\qquad\qquad\qquad\qquad\qquad\qquad\qquad\quad\:=O(N\log{N})T(N)=2T(2N)+NNT(N)=2NT(2N)+1NT(N)=2NT(2N)+12NT(2N)=4NT(4N)+1...2T(2)=1T(1)+1NT(N)=1T(1)+logNT(N)=NlogN+N=O(NlogN){T(N)=2T(2N)+NT(2N)=2T(4N)+2N{T(N)=4T(4N)+2NT(4N)=2T(8N)+4N...T(N)=2kT(2kN)+kN=NT(1)+NlogN=NlogN+N=O(NlogN)
N≠2kN≠2^kN=2k,处理起来更加复杂,但结果类似

4.问题:
在这里插入图片描述
六.快速排序
1.基本思想:

"快速排序"(Quick Sort)是在实践中最快的已知排序算法.其平均运行时间为O(N*log N);最坏情形下的运行时间为O(N^2),但稍加努力即可避免这种
情形.不过,在实践中该算法很难编程.和归并排序一样,快速排序也是1种分治的递归算法,步骤如下:
①若S中元素数为0/1,直接返回
②取S中的任意元素v,称为"枢纽元"(Pivot)
③将S-{v}以v为界分为2个不相交的子集S1={x∈S-{v}|x≤v},S2={x∈S-{v}|x≥v}
④结果为{QuickSort(S1),v,QuickSort(S2)}

在这里插入图片描述
2.实现
(1)说明:

①选取枢纽元:.选取首元素作为枢纽元:若输入是随机的,可以接受;若输入是预排序的,则时间复杂度为O(N^2),但却几乎没有进行任何操作
  Ⅱ.随机选取枢纽元:这种策略通常是安全的,除非随机数生成器有问题;另外,生成随机数通常是昂贵的,可能额外花费大量时间
  Ⅲ.三数中值分割法:选取序列的中值是很困难的,因此使用序列左端/右端/中心位置处的3个元素的中值作为枢纽元.这种方法消除了最坏情况
②分割策略:.假设所有元素互异:

(2)实现:


3.算法分析:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值