数据结构与算法
第一章 绪论
第二章 线性表
第三章 树与二叉树
第四章 图
第五章 查找
第六章 排序
第六章 内部排序
一、基本概念
排序算法的稳定性:假定在待排序的记录集中,存在多个具有相同关键字值的记录,若经过排序,这些记录的相对次序仍然保持不变,即在原序列中,ki=kj且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
二、冒泡排序
将待排序的记录看作是竖着排列的“气泡”,关键字较小的记录比较轻,从而要往上浮。
对这个“气泡”序列进行n-1遍(趟)处理。所谓一遍(趟)处理,就是自底向上检查一遍这个序列,并注意两个相比较的关键字的顺序是否正确。如果发现顺序不对,即“轻”的记录在下面,就交换它们的位置。
void BubbleSort(int n, LIST &A){
for(int i =1;i<=n-1;i++){
int sp = 0;
for (int j = n;j>=i+1;j--){
if(A[j].key < A[j-1].key){
swap(A[j],A[j-1]);
sp = 1;
}
}
if(sp == 0){
return ;//若标志位为0,说明已经有序,则直接返回
}
}
}
稳定性:稳定的排序方法
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
三、快速排序
void QuickSort(int i ,int j){
keytype pivot;
int k;
int pivotindex;
pivotindex = FindPivot(i,j);
if(pivotindex !=0){
pivot = A[pivotindex].key;
k = Partition(i,j,pivot);
QuickSort(i,k-1);
QuickSort(k,j)
}
}
int FindPivot(int i,int j){
keytype firstkey = A[i].key;
for(int k = i+1;k<=j;k++){
if(A[k].key > firstkey){
return k;
}
else if(A[k].key<firstkey) {
return i;
}
}
return 0;//若A[i]到A[j]全都相同,则返回0,否则返回前两个不同关键字中较大的。
}
int Partition(int i,int j, keytype pivot){
int l = i;r = j;
do{
while(A[r].key >= pivot){
r--;
}
while(A[l].key < pivot){
l++;
}
if(l<=r){
swap(A[l],A[r]);
}
}while(l <= r)
return l;
}
稳定性:不稳定
时间复杂度:
O
(
n
log
2
n
)
O(n \log_{2}{n})
O(nlog2n)
空间复杂度:
O
(
log
2
n
)
O(\log_{2}{n})
O(log2n)
最坏情况:
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
n
)
O(n)
O(n)
四、直接选择排序
直接选择排序与冒泡排序的区别在:冒泡排序每次比较后,如果发现顺序不对立即进行交换,而选择排序不立即进行交换,而是找出最小关键字记录后再进行交换。
void SelectSort(int n, LIST A){
keytype lowkey;
int i,j,lowindex;
for(int i = 1;i<n;i++){
lowindex = i;
lowkey = A[i].key;
for(j = i+1;j<=n;j++){
if(A[j].key<lowkey){
lowkey = A[j];
lowindex = j;
}
}
if(i != lowindex){
swap(A[i],A[lowindex]);
}
}
}
稳定性:不稳定排序
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
五、堆排序
首先将待排序的记录序列用完全二叉树表示;
然后完全二叉树构造成一个堆,此时,选出了堆中所有记录关键字的最小者;
最后将关键字最小者从堆中移走,并将剩余的记录再调整成堆,这样又找出了次小的关键字记录,以此类推,直到堆中只有一个记录。
void HeapSort(int n,LIST A){
int i;
for(i = n/2;i>=1;i--){
PushDown(i,n);
}
for(i = n;i<=2;i--){
PushDowm(1.i-1);
}
}
void PushDown(int first, int last){
int r = first;
while(r <= last/2){
if((r == last/2) && (last%2 == 0)){
if(A[r].key > A[2*r].key){
swap(A[r],A[2*r]);
}
r = last;
}else if((A[r].key>A[2*r].key)&& (A[2*r].key<=A[2*r+1].key)){
swap(A[r],A[2*r]);
r = 2*r;
}else if ((A[r].key>A[2*r].key)&& (A[2*r].key>A[2*r+1].key)){
swap(A[r],A[2*r+1]);
r = 2*r+1;
}else{
r = last;
}
}
}
稳定性:不稳定
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
六、插入排序
void InsertSort(int n,LIST A){
int i,j;
A[0].key = -infi;
for(i = 1;i<=n;i++){
j = i;
while(A[j].key<A[j-1].key){
swap(A[j].A[j-1]);
j = j-1;
}
}
}
稳定性:稳定
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
七、希尔排序
将整个待排序记录分割成若干个子序列,在子序列内分别进行直接插入排序,使整个序列逐步向基本有序发展;待整个序列中的记录基本有序时,对全体记录进行直接插入排序。
void ShellSort(int n,LIST A){
int i,j,d;
for(d=n/2;d>=1;d=d/2){
for(i = d+1;i<=n;i++){
A[0].key = A[i].key;
j = i-d;
while(j>0 && A[0].key <A[j].key){
A[j+d] = A[j];
j = j-d;
}
A[j+d] = A[0];
}
}
}
希尔排序的时间性能在O(n2)和O(nlog2n)之间
希尔排序所需的比较次数和记录的移动次数
约为O(n1.3 )
八、归并排序
void MergeSort(LIST A,LIST B,int low, int high){
int mid = (low+high)/2;
if(low<high){
MergeSort(A,B,low,mid);
MergeSort(A,B,mid+1,high);
Merge(low,mid,high,A,B);
}
}
void Merge(int s,int m,int t,LIST A,LIST B){
int i = s,j=m+1k=s;
while(i<=m && j<=t){
B[k++] = (A[i].key<=A[j].key) ? A[i++] : A[j++];
}
while(i<= m){
B[K++] = A[i++];
}
while(j <= t){
B[K++] = A[j++];
}
}
时间复杂度: O ( n log 2 n ) O(n \log_{2}{n}) O(nlog2n)
九、基数排序(桶排序)
void RadixSort(int figure, QUEUE &A){
QUEUE Q[10];
records data;
int pass,r,i;
for(pass = 1;pass<=figure;pass++){
for(int i = 0;i<=9;i++){
MAKENULL(Q[i]);
}
while(!empty(A)){
data = front(A);
dequeue(A);
r = Radix(data.key,pass);
enqueue(data,Q[r]);
}
for(i = 0;i<=9;i++){
while(!empty(Q[i])){
data = front(Q[i]);
dequeue(Q[i]);
enqueue(data,A);
}
}
}
}
时间复杂度:
O
(
d
(
n
+
r
)
)
O(d(n+r))
O(d(n+r))
空间复杂度:
O
(
n
+
r
)
O(n+r)
O(n+r)
第七章 外部排序
外部排序主要考虑访问磁盘的次数,即I/O次数
第一阶段:初始归并段形成
第二阶段:多路归并
若把内存区域等份地分为3个缓冲区。其中的两个为输入缓冲区, 一个为输出缓冲区, 可以在内存中利用简单2路归并函数MergeSort()
实现2路归并。
当输出缓冲区装满250个记录时,就输出到磁盘。
如果归并期间某个输入缓冲区空了,就立即向该缓冲区继续装入所对应归并段的一块记录信息,使之与另一个输入缓冲区的剩余记录归并,
K路平衡归并与败者树
K路归并树n个记录处理时间为
O
(
(
n
−
1
)
∗
log
2
k
)
+
O
(
k
)
=
O
(
n
⋅
log
2
k
)
O((n-1)*\log_{2}{k})+O(k) = O(n·\log_{2}{k})
O((n−1)∗log2k)+O(k)=O(n⋅log2k)
归并趟数:
O
(
log
k
m
)
O(\log_{k}{m})
O(logkm)
总时间为
O
(
n
⋅
log
2
k
⋅
log
k
m
)
=
O
(
n
⋅
log
2
m
)
O(n·\log_{2}{k}·\log_{k}{m})=O(n·\log_{2}{m})
O(n⋅log2k⋅logkm)=O(n⋅log2m)
所以K路归并的CPU时间与K无关
置换-选择排序
减少初始归并段个数m也可以减少归并趟数S
最佳归并树
使外存读写次数最少
最佳归并树应该是一棵“正则树”
若初始归并段不足以构成一棵严格k 叉树时,需要添加长度为0 的“虚段”,按照哈夫曼树的原则,权为0的叶子离树根最远。因此,最佳归并树如上所示。