数据结构之排序的方法总结及相关的复杂度分析(包括伪码描述)
数据结构之排序的方法总结及相关的复杂度
- 没有一种排序是让你在所有情况下都表现最好的
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),它几乎与关键字的空间的大小无关。也适合于关键字直接比较计算量大的问题
它以较小的装填因子为前提。因此散列方法是一个以空间换时间的方法
但是它不适合范围查找或者最大值最小值查找,相比于二叉树等。
- 七种排序方法(稳定性、空间复杂度、时间复杂度)分析总结
- 数据结构-排序: 各种排序算法全分析
- (C# 版描述)冒泡排序算法以及排序时间的测试 ---数据结构
- netlink监听网络变化代码(转载)+流程分析(原创+转载)+数据结构以及相关宏的解析(原创)
- 数据结构之排序总结3
- 数据结构中排序方法基本概念 及 分类
- 自己总结的USB数据结构及其描述符
- 数据分析常用方法总结
- 各种排序方法级复杂度总结
- yii框架中findall方法取数据使用总结,包括select各种条件,where条件,order by条件,limit限制等
- 基于R语言的数据分析和挖掘方法总结——描述性统计
- SPSS统计分析过程包括描述性统计、均值比较、一般线性模型、相关分析、回归分析、对数线性模型、聚类分析、数据简化、生存分析、时间序列分析、多重响应等几大类
- 总结描述用户和组管理类命令的使用方法,系统用户相关信息,取出主机IP地址
- 数据结构与算法总结——排序(一)简单排序
- 数据结构与算法-排序篇-Python描述
- (C# 版描述)选择排序算法以及排序时间的测试 --数据结构
- linux路由内核实现分析(二)---FIB相关数据结构(4)
- 数据结构——排序——8种常用排序算法稳定性分析
- yii框架中findall方法取数据使用总结,包括select各种条件,where条件,order by条件,limit限制以及使用单纯sql语句query时占位符的使用等
- jQuery 获取跨域XML(RSS)数据的相关总结分析