上图是最常见的几类算法的整体对比
对于稳定性做出解释,就是说值相同数据在排序完成之后他们是否会保持排序前在数列中的先后顺序
下面我们将这几种算法一一进行解析
- 插入排序
1.常规插入排序
void insertSort(int *a, int len)
{
int i, j, n, tmp;
for (i = 1; i < len; i++) {
if(a[i]<a[i-1]){
tmp = a[i];
j = 0;
while (a[j] < tmp)
j++;
for (n = i; n > j; n--)
a[n] = a[n - 1];
a[j] = tmp;
}
}
}
这种排序实现思想主体是将数据像有序数列进行插入,第一步先寻找插入位置,第二步进行数据搬移将该位置之后的数据向后搬移,第三步将数据插入
void binary_insert_sort(int a[], int len) {
int key,i, j,low, high, mid;
for(i = 1; i < len; i++) {
if(a[i] < a[i-1]) {
low = 0;
high = i - 1;
key = a[i];
while(low <= high) {
mid = (low + high) / 2;
if(key < a[mid])
high = mid - 1;
else
low = mid + 1;
}
for(j = i; j > high + 1; j--)
a[j] = a[j - 1];
a[high + 1] = key;
}
}
}
改良版问为在查找位置的方式上选用折半查找增加效率
这种排序的方法的致命缺点是在搬移数据的时候效率太低
2.插入排序
void sort(int arr[], int n, int step)
{
for (int i = step; i < n; i += step)
for (int j = i; j >= step && arr[j] < arr[j - step]; j -= step)
swap(arr[j], arr[j - step]);
}
void shellSort(int arr[], int n)
{
int i, j;
for (i = n / 2; i > 2; i /= 2)
for (j = 0; j < i; j++)
sort(arr + j, n - j, i);
sort(arr, n, 1);
}
//sort()函数对一组间隔step数进行排序
//shellsort()外循环是减小step,内循环是将所有组的数都进行sort排序
希尔排序是在基本插入排序的方法是进行分布,对相隔N个数的一批数据进行排序,然后逐渐减小N的值,最终实现有序
插入排序在对几乎已经排好序的数据操作时,效率高,既可以达到线性排序的效率
但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
步长的选择是希尔排序的重要部分,只要最终步长为1任何步长序列都可以工作
- 选择排序
1.选择排序
void selectSort(int *arr, int len)
{
int i = len;
while (i > 1){
int max = 0;
for (int j = 0; j < i; j++) {
if (arr[j] >arr[max] ) {
max = j;
}
}
swap(arr[i - 1] ,arr[max]);
i--;
}
}
对数据进行最值判断并将该值与队尾/队首数据互换,逐渐减少规模实现有序
改进方式:选择排序每次只能确定一个元素排序后的定位,我们可以改进他每次获得两个位置(最大值和最小值),这样可以减少一半的循环次数
2.堆排序
void Max_Heapify(int A[], int i, int size)
{
int largest;
if (i * 2 + 1 < size && A[i * 2 + 1] > A[i])
largest = i * 2 + 1;
else
largest = i;
if (i * 2 + 2 < size && A[i * 2 + 2] > A[i])
largest = i * 2 + 2;
if (largest != i){
swap(A[largest], A[i]);
Max_Heapify(A, largest, size);
}
}
/*建立最大堆*/
void Build_Max_Heap(int A[], int size)
{
for (int i = (size - 1) / 2; i >= 0; i--)
Max_Heapify(A, i, size);
}
void HeapSort(int A[], int size)
{
Build_Max_Heap(A, size);
for (int i = size - 1; i >= 0; i--)
{
swap(A[0], A[i]);
Max_Heapify(A, 0, i);
}
}
利用最大堆最小堆的头节点数值特性,将其与队尾位置元素进行互换并重新调整该堆(减小规模)
这里提一句,升序建大堆,降序建小堆
近似完全二叉树+向下调整
升序建大堆,降序建小堆
建堆从(HEAP_SIZE-1)/2开始逐渐减小到1,是因为这批节点都是有子节点的节点
- 交换排序
1.快速排序
void quickSort(int *arr, int low, int high)
{
int i = low;
int j = high;
int key = arr[low];
if (low >= high)
return;
while (i < j) {
while (i < j && arr[j] >= key)
j--;
arr[i] = arr[j];
while (i < j && arr[i] <= key)
i++;
arr[j] = arr[i];
}
arr[i] = key;
quickSort(arr, low, i - 1);
quickSort(arr, i + 1, high);
}
选阙值(一般为队首队尾)进行排序,小的放一边,大的放一边,然后对两部分在进行排序,利用了递归的思想
总的来说,对于快速排序算法的改进主要集中在三个方面:
1) 选取一个更好的中轴值
2)根据产生的子分区大小调整算法
3)不同的划分分区的方法
2.冒泡排序
void bubbleSort(int arr[], int count)
{
int i = count, j;
int temp;
while (i > 0)
{
for (j = 0; j < i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
swap(arr[j], arr[j + 1]);
}
}
i--;
}
}
这个再常见不过了,这里提一下改进
1.如果某一趟没有数据交换,则表示已经排好序,就可以提前终止循环
在循环之前下flag,flag在swap的时候改值,若一趟下来flag都没改变则终止
2.设置Iindex变量记录发生最后一次交换时的交换位置,说明该位置之后的数据是有序的,所有用该位置来代替i进行循环判断
冒泡排序的高级改良有一个鸡尾酒排序,有兴趣的朋友自行查阅
- 归并排序
将有二个有序数列a[first...mid]和a[mid...last]合并。
void mergearray(int a[], int first, int mid, int last, int temp[])
{
int i = first, j = mid + 1, k = 0;
while (i <= mid && j <= last)
{
if (a[i] < a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while (i <= mid)
temp[k++] = a[i++];
while (j <= last)
temp[k++] = a[j++];
for (i = 0; i < k; i++)
a[first + i] = temp[i];
}
void mergesort(int a[], int first, int last, int temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
mergesort(a, first, mid, temp); //左边有序
mergesort(a, mid + 1, last, temp); //右边有序
mergearray(a, first, mid, last, temp); //再将二个有序数列合并
}
}
bool MergeSort(int a[], int n)
{
int *p = new int[n];
if (p == NULL)
return false;
mergesort(a, 0, n - 1, p);
delete[] p;
return true;
}
归并的思想是将两个有序数列进行合并,这里要提一下归并排序需要大量的额外空间
参考文献