经典排序算法
算法分类
十种常见排序算法可以分为两大类:
比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
算法复杂度
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
冒泡排序(Bubble Sort)
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对(N-1)个元素重复以上的步骤(除了最后一个) 重复步骤1~3,直到排序完成。
#include <stdio.h>
#include <stdlib.h>
void ShowArray(int*pArr, int len);
void BabbleSort(int *pArr, int len);
int main()
{
int array[10] = {-1, 9, 5, 0, -10, 44, 77, 33, -2, -99};
int length = sizeof(array) / sizeof(array[0]);
ShowArray(array, length);
BabbleSort(array, length);
printf("排序后的数组为:");
ShowArray(array, length);
system("PAUSE");
return 0;
}
//默认为增序排序
void BabbleSort(int *pArr, int len)
{
for (int i = 0; i < len - 1; i++)
{
for (int j = i+1; j < len; j++)
{
if (pArr[i] > pArr[j])
{
int tmp = pArr[i];
pArr[i] = pArr[j];
pArr[j] = tmp;
}
}
}
}
void ShowArray(int *pArr, int len)
{
for (int i=0; i<len; i++)
{
printf("%d ", pArr[i]);
}
printf("\n");
}
选择排序(Selection Sort)
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
- 以此类推,直到所有元素均排序完毕。
#include <stdio.h>
#include <stdlib.h>
void ShowArray(int*pArr, int len);
void SelectionSort(int *pArr, int len);
int main()
{
int array[10] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int length = sizeof(array) / sizeof(array[0]);
ShowArray(array, length);
SelectionSort(array, length);
printf("排序后的数组为:");
ShowArray(array, length);
system("PAUSE");
return 0;
}
//默认为增序排序
void SelectionSort(int *pArr, int len)
{
int minIndex;
for (int i = 0; i < len - 1; i++)
{
minIndex = i;
for (int j = i + 1; j < len; j++)
{
if (pArr[minIndex] > pArr[j])
{
minIndex = j; // search min value
}
}
int tmp = pArr[i];
pArr[i] = pArr[minIndex];
pArr[minIndex] = tmp;
}
}
void ShowArray(int *pArr, int len)
{
for (int i = 0; i < len; i++)
{
printf("%d ", pArr[i]);
}
printf("\n");
}
插入排序(Insertion Sort)
工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤2~5。
#include <stdio.h>
#include <stdlib.h>
void ShowArray(int*pArr, int len);
void InsertSort(int *pArr, int len);
int main()
{
int array[10] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int length = sizeof(array) / sizeof(array[0]);
ShowArray(array, length);
InsertSort(array, length);
printf("排序后的数组为:");
ShowArray(array, length);
system("PAUSE");
return 0;
}
//默认为增序排序
void InsertSort(int *pArr, int len)
{
int preIndex, curr;
for (int i = 1; i < len; i++)
{
curr = pArr[i];
preIndex = i - 1;
while (preIndex >= 0 && pArr[preIndex] > curr)
{
pArr[preIndex + 1] = pArr[preIndex];
preIndex--;
}
pArr[preIndex + 1] = curr;
}
}
void ShowArray(int *pArr, int len)
{
for (int i = 0; i < len; i++)
{
printf("%d ", pArr[i]);
}
printf("\n");
}
归并排序(Merge Sort)
归并排序包括"从上往下"和"从下往上"2种方式
- 从下往上的归并排序:
将待排序的数列分成若干个长度为1的子数列,然后将这些数列两两合并;
得到若干个长度为2的有序数列,再将这些数列两两合并;
得到若干个长度为4的有序数列,再将它们两两合并;
直接合并成一个数列为止。 - 从上往下的归并排序:它与"从下往上"在排序上是反方向的。它基本包括3步:
① 分解 – 将当前区间一分为二,即求分裂点 mid = (low + high)/2;
② 求解 – 递归地对两个子区间a[low…mid] 和 a[mid+1…high]进行归并排序。递归的终结条件是子区间长度为1。
③ 合并 – 将已排序的两个子区间a[low…mid]和 a[mid+1…high]归并为一个有序的区间a[low…high]。
#include <stdio.h>
#include <stdlib.h>
// 数组长度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
/*
* 将一个数组中的两个相邻有序区间合并成一个
*
* 参数说明:
* a -- 包含两个有序区间的数组
* start -- 第1个有序区间的起始地址。
* mid -- 第1个有序区间的结束地址。也是第2个有序区间的起始地址。
* end -- 第2个有序区间的结束地址。
*/
void merge(int a[], int start, int mid, int end)
{
int *tmp = (int *)malloc((end-start+1)*sizeof(int)); // tmp是汇总2个有序区的临时区域
int i = start; // 第1个有序区的索引
int j = mid + 1; // 第2个有序区的索引
int k = 0; // 临时区域的索引
while(i <= mid && j <= end)
{
if (a[i] <= a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
while(i <= mid)
tmp[k++] = a[i++];
while(j <= end)
tmp[k++] = a[j++];
// 将排序后的元素,全部都整合到数组a中。
for (i = 0; i < k; i++)
a[start + i] = tmp[i];
free(tmp);
}
/*
* 归并排序(从上往下)
*
* 参数说明:
* a -- 待排序的数组
* start -- 数组的起始地址
* endi -- 数组的结束地址
*/
void merge_sort_up2down(int a[], int start, int end)
{
if(a==NULL || start >= end)
return ;
int mid = (end + start)/2;
merge_sort_up2down(a, start, mid); // 递归排序a[start...mid]
merge_sort_up2down(a, mid+1, end); // 递归排序a[mid+1...end]
// a[start...mid] 和 a[mid...end]是两个有序空间,
// 将它们排序成一个有序空间a[start...end]
merge(a, start, mid, end);
}
/*
* 对数组a做若干次合并:数组a的总长度为len,将它分为若干个长度为gap的子数组;
* 将"每2个相邻的子数组" 进行合并排序。
*
* 参数说明:
* a -- 待排序的数组
* len -- 数组的长度
* gap -- 子数组的长度
*/
void merge_groups(int a[], int len, int gap)
{
int i;
int twolen = 2 * gap; // 两个相邻的子数组的长度
// 将"每2个相邻的子数组" 进行合并排序。
for(i = 0; i+2*gap-1 < len; i+=(2*gap))
{
merge(a, i, i+gap-1, i+2*gap-1);
}
// 若 i+gap-1 < len-1,则剩余一个子数组没有配对。
// 将该子数组合并到已排序的数组中。
if ( i+gap-1 < len-1)
{
merge(a, i, i + gap - 1, len - 1);
}
}
/*
* 归并排序(从下往上)
*
* 参数说明:
* a -- 待排序的数组
* len -- 数组的长度
*/
void merge_sort_down2up(int a[], int len)
{
int n;
if (a==NULL || len<=0)
return ;
for(n = 1; n < len; n*=2)
merge_groups(a, len, n);
}
void main()
{
int i;
int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int ilen = LENGTH(a);
printf("before sort:");
for (i=0; i<ilen; i++)
printf("%d ", a[i]);
printf("\n");
merge_sort_up2down(a, 0, ilen-1); // 归并排序(从上往下)
//merge_sort_down2up(a, ilen); // 归并排序(从下往上)
printf("after sort:");
for (i=0; i<ilen; i++)
printf("%d ", a[i]);
printf("\n");
}
快速排序(Quick Sort)
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
#include <stdio.h>
#include <stdlib.h>
// 数组长度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
/*
* 快速排序
*
* 参数说明:
* a -- 待排序的数组
* l -- 数组的左边界(例如,从起始位置开始排序,则l=0)
* r -- 数组的右边界(例如,排序截至到数组末尾,则r=a.length-1)
*/
void quick_sort(int a[], int l, int r)
{
if (l < r)
{
int i, j, x;
i = l;
j = r;
x = a[i];
while (i < j)
{
while (i < j && a[j] > x)
j--; // 从右向左找第一个小于x的数
if (i < j)
a[i++] = a[j];
while (i < j && a[i] < x)
i++; // 从左向右找第一个大于x的数
if (i < j)
a[j--] = a[i];
}
a[i] = x;
quick_sort(a, l, i - 1); /* 递归调用 */
quick_sort(a, i + 1, r); /* 递归调用 */
}
}
void main()
{
int i;
int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int ilen = LENGTH(a);
printf("before sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
quick_sort(a, 0, ilen - 1);
printf("after sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
system("PAUSE");
}
堆排序(Heap Sort)
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
最大堆进行升序排序的基本思想:
- ① 初始化堆:将数列a[1…n]构造成最大堆。
- ② 交换数据:将a[1]和a[n]交换,使a[n]是a[1…n]中的最大值;然后将a[1…n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1…n-1]中的最大值;然后将a[1…n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。
(最大堆的性质)在第一个元素的索引为 0 的情形中:
性质一:索引为i的左孩子的索引是 (2i+1);
性质二:索引为i的右孩子的索引是(2i+2);
性质三:索引为i的父结点的索引是 floor((i-1)/2);
#include <stdio.h>
#include <stdlib.h>
// 数组长度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
#define swap(a,b) (a^=b,b^=a,a^=b)
/*
* (最大)堆的向下调整算法
*
* 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
* 其中,N为数组下标索引值,如数组中第1个数对应的N为0。
*
* 参数说明:
* a -- 待排序的数组
* start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
* end -- 截至范围(一般为数组中最后一个元素的索引)
*/
void maxheap_down(int a[], int start, int end)
{
int c = start; // 当前(current)节点的位置
int l = 2 * c + 1; // 左(left)孩子的位置
int tmp = a[c]; // 当前(current)节点的大小
for (; l <= end; c = l, l = 2 * l + 1)
{
// "l"是左孩子,"l+1"是右孩子
if (l < end && a[l] < a[l + 1])
l++; // 左右两孩子中选择较大者,即m_heap[l+1]
if (tmp >= a[l])
break; // 调整结束
else // 交换值
{
a[c] = a[l];
a[l] = tmp;
}
}
}
/*
* 堆排序(从小到大)
*
* 参数说明:
* a -- 待排序的数组
* n -- 数组的长度
*/
void heap_sort_asc(int a[], int n)
{
int i;
// 从(n/2-1) --> 0逐次遍历。遍历之后,得到的数组实际上是一个(最大)二叉堆。
for (i = n / 2 - 1; i >= 0; i--)
maxheap_down(a, i, n - 1);
// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for (i = n - 1; i > 0; i--)
{
// 交换a[0]和a[i]。交换后,a[i]是a[0...i]中最大的。
swap(a[0], a[i]);
// 调整a[0...i-1],使得a[0...i-1]仍然是一个最大堆。
// 即,保证a[i-1]是a[0...i-1]中的最大值。
maxheap_down(a, 0, i - 1);
}
}
/*
* (最小)堆的向下调整算法
*
* 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
* 其中,N为数组下标索引值,如数组中第1个数对应的N为0。
*
* 参数说明:
* a -- 待排序的数组
* start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
* end -- 截至范围(一般为数组中最后一个元素的索引)
*/
void minheap_down(int a[], int start, int end)
{
int c = start; // 当前(current)节点的位置
int l = 2 * c + 1; // 左(left)孩子的位置
int tmp = a[c]; // 当前(current)节点的大小
for (; l <= end; c = l, l = 2 * l + 1)
{
// "l"是左孩子,"l+1"是右孩子
if (l < end && a[l] > a[l + 1])
l++; // 左右两孩子中选择较小者
if (tmp <= a[l])
break; // 调整结束
else // 交换值
{
a[c] = a[l];
a[l] = tmp;
}
}
}
/*
* 堆排序(从大到小)
*
* 参数说明:
* a -- 待排序的数组
* n -- 数组的长度
*/
void heap_sort_desc(int a[], int n)
{
int i;
// 从(n/2-1) --> 0逐次遍历每。遍历之后,得到的数组实际上是一个最小堆。
for (i = n / 2 - 1; i >= 0; i--)
minheap_down(a, i, n - 1);
// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for (i = n - 1; i > 0; i--)
{
// 交换a[0]和a[i]。交换后,a[i]是a[0...i]中最小的。
swap(a[0], a[i]);
// 调整a[0...i-1],使得a[0...i-1]仍然是一个最小堆。
// 即,保证a[i-1]是a[0...i-1]中的最小值。
minheap_down(a, 0, i - 1);
}
}
void main()
{
int i;
int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int ilen = LENGTH(a);
printf("before sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
heap_sort_asc(a, ilen); // 升序排列
//heap_sort_desc(a, ilen); // 降序排列
printf("after sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
system("PAUSE");
}
希尔排序(Shell Sort)
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 表中每个元素每次向前移动ti个元素距离的倍数进行比较,若大于(小于)则交换位置,直到本次移动的元素出界
- 重复第三步,直到ti取1,结束交换
#include <stdio.h>
#include <stdlib.h>
// 数组长度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
/*
* 希尔排序
*
* 参数说明:
* a -- 待排序的数组
* n -- 数组的长度
*/
void shell_sort1(int a[], int n)
{
int i, j, gap;
// gap为步长,每次减为原来的一半。
for (gap = n / 2; gap > 0; gap /= 2)
{
// 共gap个组,对每一组都执行直接插入排序
for (i = 0; i < gap; i++)
{
for (j = i + gap; j < n; j += gap)
{
// 如果a[j] < a[j-gap],则寻找a[j]位置,
//并将后面数据的位置都后移。
if (a[j] < a[j - gap])
{
int tmp = a[j];
int k = j - gap;
while (k >= 0 && a[k] > tmp)
{
a[k + gap] = a[k];
k -= gap;
}
a[k + gap] = tmp;
}
}
}
}
}
/*
* 对希尔排序中的单个组进行排序
*
* 参数说明:
* a -- 待排序的数组
* n -- 数组总的长度
* i -- 组的起始位置
* gap -- 组的步长
*
* 组是"从i开始,将相隔gap长度的数都取出"所组成的!
*/
void group_sort(int a[], int n, int i, int gap)
{
int j;
for (j = i + gap; j < n; j += gap)
{
// 如果a[j] < a[j-gap],则寻找a[j]位置,并将后面数据的位置都后移。
if (a[j] < a[j - gap])
{
int tmp = a[j];
int k = j - gap;
while (k >= 0 && a[k] > tmp)
{
a[k + gap] = a[k];
k -= gap;
}
a[k + gap] = tmp;
}
}
}
/*
* 希尔排序
*
* 参数说明:
* a -- 待排序的数组
* n -- 数组的长度
*/
void shell_sort2(int a[], int n)
{
int i, gap;
// gap为步长,每次减为原来的一半。
for (gap = n / 2; gap > 0; gap /= 2)
{
// 共gap个组,对每一组都执行直接插入排序
for (i = 0; i < gap; i++)
group_sort(a, n, i, gap);
}
}
void main()
{
int i;
int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int ilen = LENGTH(a);
printf("before sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
//shell_sort1(a, ilen);
shell_sort2(a, ilen);
printf("after sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
system("PAUSE");
}