您的位置:首页 > 理论基础 > 数据结构算法

数据结构之排序的方法总结及相关的复杂度分析(包括伪码描述)

2019-02-02 13:05 501 查看

数据结构之排序的方法总结及相关的复杂度

  • 没有一种排序是让你在所有情况下都表现最好的

1.简单排序

冒泡排序的伪码描述

void Bubble Sort ( ElementType A[], int N)
{ for (p=N-1;p>=0;p--)
{ flag=0;
for (i=0;i<p;i++)//p指向的最后后一个元素地位置,也就是N-1
{ if(A[i]>A[i+1])
Swap(A[i],A[i+1]);
flag=1;
}
if(flag=0) break;//全程无交换,则说明可以直接退出不用再执行下一个了。
}
}
  • 时间复杂度:最好情况是顺序T=O(N);
    最坏情况是逆序T=O(N^2);

插入排序的伪码描述

void Insertion Sort (ElementType A[], int N)
{   for (p=1;p<N;p++)//p从1开始因为默认手里已经有一张牌
{ Tem=A

;//摸下一张牌 for(i=p;i>0&&A[i-1]>Temp;i--) { A[i]=A[i-1]; //移出空位 } A[i]=Temp; //新牌落位 } }

  • 时间复杂度:最好情况是T=O(N);
    最坏情况是T=O(N^2);
  • 时间复杂度下界:
    交换2个相邻元素正好消去1个逆序对
    所以插入排序:T=O(N+I);如果序列基本有序,则插入排序简单且高效。
    定理:任意N个不同元素组成的序列平均具有N*(N-1)/4个逆序对。
    定理:任何仅以交换相邻两元素排序的算法,其平均时间复杂度为T(N^2)
    这意味着:要提高算法效率,我们必须每次消去不止1个逆序对。可以每次交换两个间隔较远的两个元素
    希尔排序(shell sort)
  • 首先定义增量序列,然后对每个D_k 进行“D_k-间隔”排序
    注意:“D_k-间隔”有序地序列,在执行“D_k-1-间隔”排序后,仍然是“D_k-间隔”有序的。
    希尔排序的伪码描述
void Shell_Sort (ElementType A[], int N)
{   for(D=N/2;D>0;D/=2){  //希尔增量排序
for (p=D;p<N;p++)//插入排序的方法,把所有的1换成D,
{ Tem=A[p];//摸下一张牌
for(i=p;i>=D&&A[i-D]>Temp;i-=D)
{ A[i]=A[i-D]; //移出空位
}
A[i]=Temp; //新牌落位
}
}
}
[p]最坏的情况:T=sita(N^2)

2.堆排序

选择排序

void Selection_Sort (ElementType A[], int N)
{ for (i=0;i<N;i++)
{ MinPosistion = ScanForMin(A, i, N-1);//从A[i]到A[N-1]中找最小元,将其位置赋给MinPosition
Swap(A[i],A[MinPosition]);//将未排序部分的最小元换到有序部分的最后位置。
}
}

无论如何:T=sita(N^2)
降低时间复杂度的关键就是如何找到最小元。采用最小堆方法。
堆排序算法

void Heap_Sort (ElementType A[], int N)
{ for (i=N/2;i>=0;i--) //bulidHeap
PercDown (A,i,N);//调用一个向下过滤的子函数,i对应根结点的2位置,N代表当前堆有多少个元素,通过此函数可以将一个最大堆建立
for (i=N-1;i>0;i--)
{ Swap(&A[0],&A[i]);//DeleteMax
PercDown(A,0,i);
}
}

定理:堆排序处理N个不同元素的随机排列的平均比较次数是:

2NlogN-O(NloglogN)

3.归并排序

核心:有序子列的归并
归并的时间复杂度是:T(N)=O(N)
有序子列的归并伪代码描述:

//L=左边起始位置,R=右边起始位置,RightEnd=右边终点位置
void Merge (ElementType A[],ElementType TmpA[], int L, int R, int RightEnd)
{ LeftEnd =R-1;//左边终点位置,假设左右两列挨着
Tmp=L;存放结果的数组的初始位置
NumElements=RightEnd-L+1;
while(L<=LeftEnd && R<=RightEnd){
if (A[L]<=A[R])
TmpA[Tmp++]=A[L++];
else
TmpA[Tmp++]=A[R++];
}
while (L<LeftEnd)
TmpA[Tmp++]=A[L++];
while (R<RightEnd)
TmpA[Tmp++]=A[R++];
for(i=0,i<NumElements;i++,RightEnd--)
A[RightEnd]=TmpA[RightEnd];//因为Leftend被改变了,所以在将临时数组导到数组A时从右边开始倒入。
}
3.1 递归算法
  • 分而治之
void MSort (ElementType A[],ElementType TmpA[],int L, int RightEnd)
{ int Center;
if (L<RightEnd)
{ Center= (L+RightEnd)/2;
MSort(A,TmpA,L, Center);
Msort(A,TmpA,L,Center+1,RightEnd)
Merge(A, TmpA, L,Center+1,RightEnd);
}
}

时间复杂度是比较强的T(N)=O(NlogN)
它是一个比较稳定的算法
统一函数接口

void Merge_sort(ElementType A[], int N)
{ ElementType *TmpA;
TmpA=malloc(N *sizeof(ElementType));
if (TmpA!=NULL)
{ MSort(A, TmpA, 0,N-1);
}
else Error("空间不足");
}
3.2非递归算法
void Merge_sort(ElementType A[], int N)
{ int length=1;//初始化子序列长度
ElementType *TmpA;
TmpA= malloc(N * sizeof(ElementType));
if(TmpA!=NULL)
{ while (lenght<N)
{Merge_pass(A,TmpA,N,length);
length *=2;
Merge_pass(TmpA,A,N,length);
length *=2;
}
free(TmpA);
}
else Error("空间不足");
}

其中的Merge_pass

void Merge_pass (ElementType A[],Elementtype TempA[],int N, int length)//length等于当前有序子列的长度
{
for (i=0;i<=N-2*length;i+=2*length)}
Merge1(A,TmpA,i,i+length, i+2*length-1);
if (i+length<N)//归并最后2个子列
Merge1(A,TmpA,i,i+length,N-1);
else  //最后只剩一个子列
for (j=i;j<N;j++)
TmpA[j]=A[j];

在内部排序中一般不适用归并排序,一般在外排序地时候才会使用
归并排序的好处:稳定,时间复杂度是NlogN
不好的地方,需要一个额外地空间。

4.快速排序

算法概述:分而治之

void QucikSort(ElementType A[], int N)
{ if(N<2) return;
pivot=从A[]中选一个主元;//关键
将S= {A[]\pivot} 分成2个独立子集:
A1={a 属于S|a<=pivot}和A2={a属于S|a>=pivot};
A[]=QuickSort{A1,N1}并{pivot}并QuickSort(A2,N2);

}

快速排序算法最好的情况是每次正好中分,时间复杂度是T(N)=O(NlogN)
选主元:方法很多其中1种——取头中尾的中位数。
注意:快速排序的问题:用了递归的方法,对于小规模的数据(例如N不到100)可能还不如插入排序快。
因此对于快速排序而言,的解决方案是:当递归的数据规模充分小,则停止递归,直接调用简单排序,例如插入排序

void Quciksort(ElementType A[],int Left, int Right)
{if (Cutoff<=Right-Left)
{Pivot=Median3(A,Left,Right);//选主元:中位数
i=Left;j=Right-1;
for(;;)
{while (A[++i]<Pivot){}
while (A[--j]>pivot){}
if (i<j)
Swap(&A[i],&A[j]);
else break;
}
Swap (&A[i],&A[Right-1]);
Quciksort(A,Left,i-1);
Quciksort(A,i+1,Right);
}
else
Insertion_Sort(A+left,Right-Left+1);
}
//为了统一接口则变为下面
v
2add6
oid Qucik_Sort(ElementType A[],int N)
{ Quicksort(A,0,N-1);//引用上面写的函数
}

5.表排序

  • 间接排序:不移动元素本身,只移动指针的排序方法
    定义一个指针数组作为表(table)
    N个数字的排列由若干个独立环组成
    复杂度分析

6.基数排序

  • 桶排序 当每个整数选的值M比较小时,则桶排序的时间复杂度是O(N+M)
  • 基数排序 可以处理整数
  • 基数排序还可以用来处理多关键字的排序:次位优先(LSD)和主位优先(MSD)。

第6章 散列查找

1.散列表

编译处理时涉及变量和属性的管理:插入(新变量定义)和查找(变量的引用).动态查找问题
查找的本质;已知对象找位置。

  • 直接算出对象的位置:散列的思想
    散列查找法的两项基本工作:(1)计算位置:构造散列函数确定关键词存储位置(2)解决冲突:应用某种策略解决多个关键词位置相同的问题
    时间复杂度几乎是常量:O(1),即与查找时间和问题规模无关
  • 数字散列函数的构造:
    (1)直接定址法
    (2)除留余数法 如h(key)=key mod p
    (3)数字分析法:分析数字关键字在各位上的变化情况,取比较随机的位作为散列地址。
    (4)折叠法:把关键词分割成位数相同的几个部分,然后叠加
    (5)平方取中法:思想是希望能够被更多的位数所影响。
  • 字符关键词的散列函数的构造
    (1)一个简单的散列函数——ASCII码加和法
    (2)简单的改进——前进3个字符移位法
    (3)好的散列函数——移位法。思想:将其看做32进制数,每一个乘以32的多少次方,从而计算出一个比较大的数,然后这个大的数再使用取余来求。需要注意的是为了提高计算效率,这里的乘方不用乘法,可以用移位来做,比如x乘以32就相当于将x左移5位。
  • 散列查找的处理冲突的方法
    常用的处理思路:换个位置:开放地址法;同一位置的冲突对象组织在一起:链地址法
  • 散列表查找性能分析
    ASLs:成功平均查找长度 ASLu:不成功平均查找长度
    开放地址法:
    (1)线性探测:会出现聚集现象
    ASLs:成功平均查找长度(每一个元素进行冲突次数加1来进行计算)
    ASLu:不成功平均查找长度(按类进行分析,直到找到空格位置确认确实不存在)
    (2)平方探测法——二次探测: 定理:如果散列表长度TableSize是某个4k+3(k是正整数)形式的素数时,平方探测法就可以探查到整个散列表空间。
    (3)双散列
    (4)再散列
    实用的最大装填因子一般取0.5-0.85
    分离链接法:将相应位置上冲突的所有关键词存储在同一个单链表中。

这种方法是顺序存储和链式存储的结合,链表部分的存储效率和查找效率都比较低。

散列表性能分析

影响因素:散列函数是否均匀;处理冲突的方法;散列表的装填因子

散列查找特点:散列查找效率期望是常数O(1),它几乎与关键字的空间的大小无关。也适合于关键字直接比较计算量大的问题
它以较小的装填因子为前提。因此散列方法是一个以空间换时间的方法
但是它不适合范围查找或者最大值最小值查找,相比于二叉树等。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐