1-排序简单介绍
排序:将无序键重新排列,使之有序
键集:键的集合,这里默认待排序的数据都是唯一的,即不存在重复数据,这是一种理想情况,通常不作为重点
有序:升序和降序
内排序和外排序:内存数据排序和外存数据排序(通常外排序是通过调入内存中实现排序)
稳定性:假设某次待排序的数据中,存在相同数据,排序前其物理顺序与排序后物理顺序不变,即对于所有数据,任意排序,只要依然用这种算法,其顺序是不会改变的
三大类六种排序算法:
(1):直接插入排序,希尔排序
(2):直接选择排序,堆排序(利用完全二叉树)
(3):直接交换排序(冒泡排序),快速排序
对每一种排序算法,通常需要从下面几个方面研究:
(1):算法
(2):C代码的实现
(3):算法的时间复杂度
(4):算法的极端效率分析(最优情况,最劣情况)
(5):稳定性分析
2-直接插入排序
for (i = 1; i < count; i++) {
tmp = data[i];
for (j = 0; j < i && tmp >= data[j]; j++) {
}
for (t = i; t > j; t--) {
data[t] = data[t - 1];
//在j ~ i-1内的所有元素从后向前依次后移一位
}
data[j] = tmp;
}
}
3- 直接选择排序
for (i = 0; i < count - 1; i++) {
minIndex = i;
for (j = i; j < count; j++) {
if (data[j] < data[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
tmp = data[i];
data[i] = data[minIndex];
data[minIndex] = tmp;
}
}
4-直接交换排序(冒泡排序)
int hasSwap = 1;
for (i = 0; hasSwap && i < count - 1; i++) {
hasSwap = 0;
for (j = 0; j < count - i - 1; j++) {
if (data[j] > data[j+1]) {
tmp = data[j];
data[j] = data[j+1];
data[j+1] = tmp;
hasSwap = 1;
}
}
}
对一组待排序数,每次相邻的两个数比较,这两个数中,将数值大的放在后面,数值小的交换到前面,第一轮交换结束后,最后一个数就是待排序数列中的最大数,第二轮后,倒数第二个数就是第二大的数,直到只剩下一个数时停止
5- 希尔排序(改良的直接插入排序)
void shellOneSort(int *data, int count, int start, int step) {
int i;
int j;
int t;
int tmp;
for(i = start + step; i < count; i+=step) {
//每次将下标为start+=step的元素放入一组
tmp = data[i];
for(j = start; j < i && tmp >= data[j]; j += step){
//本组开始进行直接插入排序
; //遍历依次找到合适位置
}
for(t = i; t > j; t -= step) {
//要交换的两个值中间间距为step
data[t] = data[t - step];
//则这两个值中间的元素依次后移step位
}
data[j] = tmp;
}
}
void shellSort(int *data, int count) {
int start;
int step;
for(step = count/2; step; step/2) {
//计算step,step不能小于0
for(start = 0; start < step; start++) {
//控制分组的次数,step为几,就有几个start
shellOneSort(data, count, start, step);
//若干个start,每次对一组直插排序
}
}
}
直接插入排序是步长为1的希尔排序
6-堆排序(改良的直接选择排序)
堆排序基于直接选择排序,使用了完全二叉树的概念
基于 大根堆(升序) 小根堆(降序) 完成排序
大根堆:一颗完全二叉树,它的任意一个非子叶结点的值,都比其左右子树中的最大值都大(小根堆参考此概念)
事实上,任意一个连续的,有n个元素的一维数组,都可以看成一颗完全二叉树,且存在如下特点:(在下标为0的前提下)
若某个非子叶结点的下标为 i:则其左孩子下标为 2 * i + 1,
右孩子下标为 2 * i + 2,下标最大的非子叶结点下标值为 n / 2 - 1
用大根堆排序的过程:
1-将数组内数据依次插入完全二叉树中,建立一个大根堆
2-将大根堆进行调整,直到满足:父结点的值一定大于左右孩子的值
3-将最后一个子叶结点的数与最大值(最上面的根结点)互换位置,并将最大数所在的子叶结点从大根堆中取出
4-取出后,继续调整大根堆,执行第二步,让大数先上浮,小数下沉
调整结束后,再执行第三步,直到排序完成
初始化一个大根堆
用大根堆完成堆排序
void adjustHeap(int *data,int count, int root) {
int left;
int right;
int maxNode;
int tmp;
while(root <= count/2 - 1) {
// count/2-1是编号最大的非子叶结点,
//包括它和比它大的都是非子叶结点
left = 2*root - 1;
right = left + 1;
//该非子叶节点的左右孩子下标
maxNode = right >= count ? left
:(data[left] > data[right]
? left : right);
//比较某非子叶结点左右孩子大小,右孩子下标不可能大于等于count
//就是左孩子为最大,或比较值,输出值较大的下标
maxNode = data[root] > data[maxNode]
? root : maxNode;
//比较该非子叶节点的值和maxNode,确定新的maxNode
if(maxNode == root) {
return;
//非子叶结点的值为最大值,则什么也不做
}
tmp = data[root];
data[root] = data[maxNode];
data[maxNode] = tmp;
//若非子叶点值较小,则交换值
root = maxNode;
//令新的非子叶结点的值为maxNode
//data[0]是根结点,此处root意为每次进行判断的非子叶结点
}
}
void headSort(int *data, int count) {
int root;
int tmp;
for(root = count / 2 - 1; root > 0; root--){
adjustHeap(data, count, root);
//从最后一个非子叶结点向上遍历
//直到只剩最上面的一个元素,
//每次adjust该结点的值及其左右孩子的值,
//令大的值上浮,小的值下沉
};
while(count) {
adjustHeap(data, count, 0);
//此时调整最上面元素的值变为最大值
tmp = data[0];
data[0] = data[count - 1];
//取出当前数组末尾子叶结点的小值
data[count - 1] = tmp;
//将本次取出的最大数放入数组最后面
--count;
//每次得到一个最大值后,
//去掉一个最后面的子叶结点
//数组长度减一
}
}
7- 快速排序
void quickOnce(int *data, int count, int head, int tail) {
int tmp = data[head];
int start = head;
int end = tail;
if (head >= tail) {
return;
}
while (head < tail) {
while (head < tail && data[tail] >= tmp) {
--tail;
}
if (head < tail) {
data[head] = data[tail];
++head;
}
while (head < tail && data[head] <= tmp) {
++head;
}
if (head < tail) {
data[tail] = data[head];
--tail;
}
}
data[head] = tmp;
//确定第一次当作基准数的位置
quickOnce(data, count, start, head - 1);
quickOnce(data, count, head + 1, end);
//对当前tmp左右的两组数再进行递归快排,
//直到若干组数每组数只剩两个
}
void qucikSort(int *data, int count) {
quickOnce(data, count, 0, count - 1);
}
8- 排序整合
通过指向函数的指针,定义一个数组存放排序类型,并用宏定义好排序名,在使用时,输入排序方式所对应的下标,可以很方便的使用