先是第八章 线性时间排序
-
排序算法的下界
最坏情况的下界:
定理一:在最坏的情况下,任何比较排序算法都需要做下界 为nlgn次比较
定理二:堆排序和归并排序都是渐近最优的比较排序算法
练习:
8.1-1
最少进行n-1次比较,所以深度最小是n-1
8.1-2
斯特林近似公式
8.1-3
key在于n!<=l<=2^h
8.1-4
每个子序列有k!种排列方式
/k个子序列就有(k!)^(n/k)种排列方式 -
计数排序
基本思想:对于每一个输入的元素x,确定小于x的元素个数,这时就可以确定x在输出数组的位置。
伪代码:
COUNTING-SORT( A , B ,k)
1. let C[0...K] be a new array
2. for i =0 to k
3. C [ i ] = 0
4. for j = 1 to A.length
5. C[A [ j ] ] = C [ A[ j ] ]+1
6. // C[ i ] now contains the number of elements equal to i
7.for i = 1 to k
C[ i ]+=C[i -1]
7. //C[i] now contains the number of elements less than or equal to i
8. for j = A.length downto 1
9. B[ C[ A [j] ] ] = A[ j ]
10. C[ A[ j ] ]--;
代码实现:
void coutingsor(int A[],int length,int B[], int k)
{
int C[max];
for(int i = 0 ;i < k;i++)
C[i] = 0;
for(int i=0;i<length;i++)
C[A[i]]++;
for(int i=1;i<=k;i++)
C[i] = C[i]+C[i-1];
for(int j=length;j>=1;j++)
{
B[C[A[j]]=A[j];
/**
下一行是因为这个算法开始假定的是所有元素互异,但实际并不如如此理想
将一个A[j]的值放入数组B之后,都有将C[A[j]]的值--,
这样当遇到下一个相同的元素,则可以直接放入A[j-1]
**/
C[A[j]]=C[A[j]]-1;
}
}
算法分析:
计数排序是一个稳定的排序算法。
当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。
当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。
练习:
写书上
- 基数排序
算法思想:从低位开始将待排序的数按照这一位的值放到相应的编号为0~9的桶中。等到低位排完得到一个子序列,再将这个序列按照次低位的大小进入相应的桶中,一直排到最高位为止,数组排序完成。
分类:
LSD——从低位向高位排
MSD——从高位向低位排 - 主要是
- 桶排序
伪代码:
1. BUCKET-SORT( A )
2. let B[0,....n-1] be a new array
3. for i = 0 to n-1
4. make B[i] an empty list
5. for i=1 to n-1
6. insert A[i] into list B[i]
7. for i=0 to n-1
8. sort list B[i] with insertion sort
9. concatement the list B[0],B[1],....B[n-1] together in order
代码:
/*算法:桶排序*/
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void bksort(float A[],int l,int h)
{
int size = h-l+1;
vector<float> b[size];//有size个数据,就分配size个桶
for(int i=l;i<=h;i++)
{
int bi = size*A[i];//元素A[i]的桶编号
b[bi].push_back(A[i]);//将元素A[i]压入桶中
}
for(int i=0;i<size;i++)
sort(b[i].begin(),b[i].end());//桶内排序
int idx = l;//指向数组A的下标
for(int i=0;i<size;i++)
{//遍
for(int j=0;j<b[i].size();j++)
{
A[idx++] = b[i][j];
}
}
}
int main()
{
float A[] = {0.78,0.17,0.39,0.26,0.72,0.94,0.21,0.12,0.23,0.68};
bksort(A,2,9);
for(int i=0;i<10;i++)
cout<<A[i]<<" ";
}
第七章 快速排序
简要介绍:
- 是一个Divide and Conquer
- 是原址排序
- 通过选取一个主元,把数组划分成两个子数组,第一个数组的元素都比主元元素小,第二数组都大于主元元素。
二分过程
- Divide
将A[ p … r ]划分成 A[ p…q-1] 和 A[ p…r] - Conquer:
由于进行的是原址排序,不需要合并过程 - Combine:
不重要
快排伪代码:
QUICKSORT(A , p, r)
1. if p < r
2. q = PATITION( A ,p , r)
3. QUICKSORT(A , p , q - 1)
4. QUICKSORT(A , q , r)
关键的划分伪代码:
PARTITION (A , p ,r)
1. x = A [ r ]
2. i = p -1
3. for j = p to r-1
4. if A [ j ] <= x
5. i++;
6. exchange A[ i ] with A [ j ]
7. exchange A[ i +1 ] with A [ r ]
8. return i+1
代码实现
TIME = Θ(n)
分析:
快排的优秀性能体现在于平均情况时间
假设数组里面的元素都是互异的
-
T(n)= worst-case time
最坏的情况当然是当数组的数据有序(顺序or逆序)或者划分导致另一半的子数组没有元素
来分析T(n)
so T( n ) = T (0) +T(n-1)+Θ(n) -----Θ(n)为划分数组的时间
= T (n -1) +Θ(n)
这明显的
T(n) = Θ(n^2)
用递归树的方法解出来的也是如上结果 -
T(n) =best-case time (其实一般不讨论最好情况,emm)
最好的划分情况当然是 n/2 :n/2
T(n) =T(n/2) +T(n/2)+Θ(n)
=Θ(nlgn) -
T(n) = 平时情况
假设划分为 1/10 :9/10(或者任意常数划分)
T(n)= T(n/10) +T(9n/10)+Θ(n)
用递归树解的T(n ) <=cnlgn
用一种更通俗的说法,我们直觉的感受到,好坏的划分是同时交替出现在书的各层上,
- if Lucky L(n) = 2 Unlucky
- =2 Unlucky(n /2)+Θ(n)
-if Unlucky U(n) = Lucky(n-1)+Θ(n)
用解方程和主方法解的case:Θ(nlgn)
随机化快速排序
在分析平均情况的性能时,我们总是假设输入的数据的所有排列都是等概率的,而实际情况并如人所愿,但我们可以通过随机化数组或者随机选择主元的方式达到等概率的目的
伪代码:
RANDOMIZED-PARTITION(A , p, r)
1. i = RANDOM ( p,r)
2. exchange A[ r ] with A [ i ]
3. return PARTITION(A .p, r)
RANDOMIZED-QUICKSORT( A ,p ,r)
1. if p < r
5. q = RANDOMIZED-PARTITION(( A ,p , r)
6. RANDOMIZED-QUICKSORT(A , p , q - 1)
7. RANDOMIZED-QUICKSORT(A , q , r)