1.简单选择排序
1.1 基本思想
- 在待排序的数据中选出最大(小)的元素放在其最终的位置
1.2 基本操作
- 首先通过n-1次关键词的比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换。
- 重复上述操作,共进行n-1次排序,排序结束
- 在通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换
1.3 算法描述
public static void selectSort(int[] arr){
int n = arr.length;
for(int i =0;i<n;i++){
int m = i;
for(int j=i+1;j<n;j++){
if(arr[j] <arr[m]){
m=j;
}
}
if(m!=i){
int temp = arr[i];
arr[i] = arr[m];
arr[m] = temp;
}
}
}
void SelectSort(Sqlist &L){
for(i=1;i<L.length;i++){
k=i;
for(j=i+1;j<=L.length;j++){
if(L.r[j].key <L.r[i].key) k=j;
if(k!=i) L.r[k] <--> L.r[i]; //交换
}
}
}
1.4 简单选择排序算法分析
- 时间复杂度
- 稳定性
- 简单选择排序是不稳定排序
- 记录移动次数
- 最好情况:0
- 最坏情况:3(n-1)
- 比较次数
- 无论待排序状态如何,比较次数为n(n-1)/2
- 稳定性
2. 堆排序
2.1 堆
2.1.1 定义
- 若n个元素序列{a1,a2,a3.....an}满足
- 大根堆
- ai ≥ a(2i)
- ai ≥ a(2i+1)
- 小跟堆
- ai ≤ a(2i)
- ai ≤ a(2i+1)
- 从堆的定义中可以看出,堆实质是满足如下性质的完全二叉树:二叉树中任一非叶子结点均小于(大于)他的孩子结点。
- 大根堆
2.2 堆排序
- 若在输出堆顶的最小值(最大值)后,使得剩余的n-1个元素的序列重新建成一个堆,则得到n个元素的次小值... 如此反复,使得得到一个有序序列,这个过程称之为堆排序
2.2.1 堆排序中需要解决的问题
- 如何有一个无序序列建成一个堆?
- 如何在输出堆顶元素后,调整剩余元素为一个新的堆?
2.2.2 堆的重新构造
- 小跟堆
- 输出堆顶元素后,以堆中最后一个元素替代之
- 然后将根节点的值与左右子树的根节点值进行比较,并与其中小者进行交换
- 重复上述操作,直至叶子结点,将得到新的堆,成这个从堆顶至叶子的调整过程为“筛选”
- 大根堆
- 输出堆顶元素后,以堆中最后一个元素替代之
- 然后将根节点的值与左右子树的根节点值进行比较,并与其中大者进行交换
- 重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”
2.2.3 堆的调整过程
2.2.4 筛选过程的算法描述
void HeapAdjuest(elem R[],int s ,int m){
// 已知R[s...m]中记录的关键字除R[s]之外均满足堆的定义,本函数调整R[s]的关键字,是R[s....m]成为一个大根堆
rc = R[s];
for(int i =2*s;j<=m;j*=2){ //沿key较大的孩子结点向下筛选
if(j<m &&R[j] <R[j+1]) j++;//j为key较大的记录的下标
if(rc >= R[j]) break;
R[s] = R[j]; //rc应插在位置s上
s=j
}
R[s] = rc;
}
public static void heapAdjust(int[] arr,int parent,int length){
int temp = arr[parent];//存储根节点
for(int i=2*parent+1;i<=length;i++){ //从根节点重构大顶堆
if(i<length && arr[i] <arr[i+1]) i++;
if(temp >= arr[i]) break;
arr[parent] = arr[i];
parent=i;
}
arr[parent] =temp;
}
2.2.5 堆的建立
- 显然,单结点的二叉树是堆,在完全二叉树中所有以叶子结点为跟的子树是堆,这样我们只需要将以序号n/2,n/2-1,......,1的结点为根的子树均调整为堆即可
- 即:对应由n个元素组成的无序序列,筛选只需要从第n/2个元素开始
- 由于堆的实质上是一个线性表,那么我们可以顺序存储一个堆。
- 堆建立过程
- 再将以序号为n/2-3的结点为跟的二叉树调整为堆
- 再将以序号n/2-2的结点为根的二叉树调整为堆
- 将以序号n/2-1的结点为跟的二叉树调整为堆
- 调整从第n/2个元素开始,将以该元素为跟的二叉树调整为堆
2.3 算法表述
void HeapSort(elem R[]){
int i;
for(i<n/2;i>=1;i--)
heapAdjust(R,i,n);
for(i=n;i>1;i--){
Swap(R[1],R[i]);
HeapAdjust(R,1,i-1)//对R[1]到R[i-1]重新建堆
}
}
public static void heapSort(int[] arr){
int n = arr.length-1;
for(int i=(n-1)/2;i>=0;i--){
//构建大顶堆
heapAdjust(arr,i,n);
}
for(int i=n;i>0;i--){
//元素交换;
swap(arr,i);
//重新构建大顶堆
heapAdjust(arr,0,i-1);
}
}
public static void heapAdjust(int[] arr,int parent,int length){
int temp = arr[parent];
for(int i=2*parent+1;i<=length;i++){
if(arr[i] <arr[i+1] && i<length) i++;
if(temp >= arr[i])break;
arr[parent] = arr[i];
parent = i;
}
arr[parent] = temp;
}
public static void swap(int[] arr,int i){
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
}
2.4 堆排序算法分析
- 初始化所需时间不超过O(n)
- 不稳定排序
- 空间复杂度O(1)
- 堆排序的时间主要消耗在初始建堆和调整新建堆时进行的反复筛选上。堆排序在最坏情况下,其时间复杂度也为O(nlg2(n)),这是堆排序的最大优点。无论是正序还是逆序,都不会使堆排序处于最好或者最坏情况下
- 总体时间复杂度 Tw(n) = O(n)+O(nlog(n)) = O(nlog(n))
- 排序阶段
- 一次重新堆化所需时间不超过O(log(n))
- n-1次循环所需要时间不超过O(nlog(n))