五分钟掌握选择排序与其改良排序(希尔排序),内含详细手工过程图解与代码注释
1、直接选择排序
·手工过程:
例:
()9 5 1 4 6 7 8 5 3 0 9
(0)9 5 1 4 6 7 8 5 3 9
(0 1)9 5 4 6 7 8 5 3 9
(0 1 3)9 5 4 6 7 8 5 9
(0 1 3 4)9 5 6 7 8 5 9
(0 1 3 4 5)9 6 7 8 5 9
(0 1 3 4 5 5)9 6 7 8 9
(0 1 3 4 5 5 6)9 7 8 9
(0 1 3 4 5 5 6 7)9 8 9
(0 1 3 4 5 5 6 7 8)9 9
(0 1 3 4 5 5 6 7 8 9)9
(0 1 3 4 5 5 6 7 8 9 9)
时间复杂度:总共需要选择n-1次,每次都要需要固定的比较操作和可能的交换操作。
总比较次数:n-1+n-2+n-3+…+2+1 = O(n^2/2)
最优情况:完全顺序,整个过程只有比较,没有交换,比较次数固定。
最差情况:完全逆序
void directSelectSort(int* arr, int count) {
int i;//待排数据下标
int j;//待排数据后的数据下标,从i+1开始
int tmp;
int minIndex;
for(i = 0; i < count; i++) {
minIndex = i;
for(j = i + 1; j < count; j++) {
if(arr[j] < arr[minIndex]) {
minIndex = j; //更新本轮持有的最小数据的下标
}
}
if(i != minIndex) {//一轮比较完成,判断是否需要交换
tmp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = tmp;
}
}
}
·实例验证
void main() {
int arr[11] = {9, 5, 1, 4, 6, 7, 8, 5, 3, 0, 9,};
printf("排序前:");
show(arr, 11);
directSelectSort(arr, 11);
printf("排序后:");
show(arr, 11);
}
运行结果:
2、改良的直接选择排序——堆排序
堆的本质是完全二叉树,满足以下要求:
完全二叉树的任意一节点(非叶子节点)值都比左右孩子的值大。
·手工过程与详细图解
4 8 2 9 1 0 5 7 6 3
首先,从最后一个非叶子节点依次往前,作为根节点,进行所在二叉树的节点的比较与交换。若存在交换,则,先交换数据,再将交换后孩子节点作为根节点,再进行二叉树节点的比较与交换。直到不存在交换或,节点为叶子节点即停止。
最后我们发现,本轮将树中的最大值交换到了该完全二叉树的根(下标为0)。于是,将该最大值与最后一个叶子接点交换位置并剔除;再从当前二叉树的根节点出发执行一次比较与交换操作。
第二轮将8剔出,以此类推至剔除至剩1个节点,堆排序完成。
·算法介绍与具体代码实现
针对“从当前根节点,进行所在二叉树的节点的比较与交换。若存在交换,则,先交换数据,再将交换后孩子节点作为当前根节点,再进行该根节点所在二叉树的比较与交换。直到不存在交换或,当前根节点为叶子节点即停止”这一操作,给出函数adjustOneRoot(int * arr, int count, int root)。
其中用到的完全二叉树算法解释:
(左上角为该节点在数组中的下标)
整个节点数为count,最后一个非叶子节点下标为lastIndex,根节点下标为root,左右孩子下标分别为leftChild、rightChild,有:
lastIndex = count/2 - 1;
leftChild = 2root + 1;
rightChild = 2root + 2;若rightChild >= count,则不存在右孩子;
判断该节点为非叶子节点的条件:root < count/2成立则满足;
//针对单个根节点进行比较与交换
void adjustOneRoot(int * arr, int count, int root) {
int leftChildIndex;
int rightChildIndex;
int maxChildIndex;
int tmp;
while(root < count/2) {
leftChildIndex = 2*root + 1;
rightChildIndex = 2*root + 2;
maxChildIndex = (
rightChildIndex < count ?
(arr[rightChildIndex] > arr[leftChildIndex] ? rightChildIndex : leftChildIndex) :
leftChildIndex);//找出最大孩子并考虑右孩子不存在的可能
if(arr[root] < arr[maxChildIndex]) {//执行交换,并更新根节点
tmp = arr[root];
arr[root] = arr[maxChildIndex];
arr[maxChildIndex] = tmp;
root = maxChildIndex;
} else {
return;//无需交换,结束本轮操作
}
}
}
//堆排序,还原手工过程
void heapSort(int *arr, int count) {
//从最后一个非叶子节点依次往前,作为根节点,进行节点判断
int root;
int tmp;
for(root = count/2 - 1; root >= 0; root--) {
adjustOneRoot(arr, count, root);
}
//接下来交换并剔除下表为0的根
tmp = arr[count - 1];
arr[count - 1] = arr[0];
arr[0] = tmp;
count--;
for( ; count > 1; count--) {
adjustOneRoot(arr, count, 0);
tmp = arr[count - 1];
arr[count - 1] = arr[0];//滞后自减
arr[0] = tmp;
}
}
·实例验证
void main() {
int arr[10] = {4, 8, 2, 9, 1, 0, 5, 7, 6, 3,};
printf("排序前:");
show(arr, 10);
heapSort(arr, 10);
printf("排序后:");
show(arr, 10);
}
运行结果