目录
一、排序的概念
二、常见排序算法的实现
2.1 简单插入排序
2.2 希尔排序
2.3 简单选择排序
2.4 堆排序
2.5 冒泡排序
2.6 快速排序
2.6.1 hoare法
2.6.2 挖坑法
2.6.3 前后指针法
2.6.4 三路划分法
2.6.5 快速排序非递归法
2.6.6 快速排序三数取中优化
2.7 归并排序
2.7.1 递归法
2.7.2 非递归法
2.7.3 优化——小区间优化
2.8 计数排序
三、排序算法复杂度及稳定性分析
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
类似于日常打扑克摸牌的过程,摸到对应的大小就插入到牌中对应的地方。
一般我们采用从后往前比较,这样可以一边比较一边挪动元素。
void InsertSort(int* a, int n)
{
for (int i = 1; i < n; i++)
{
int end = i - 1;
int tmp = a[i];
while (end >= 0)
{
if (a[end] > tmp)
{
//往后移动
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
希尔排序:希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
//希尔排序(缩小增量排序)O(N^1.3)
//一组一组排(三层循环)
//多组并排(两层循环)
void ShellSort(int* a, int n)
{
//1.gap > 1 预排序
//2.gap == 1 直接插入排序
int gap = n;
while (gap > 1)
{
//设置gap的值
//一般分为三组
gap = gap / 3 + 1;
//对gap组进行排序
for (int j = 0; j < gap; j++)
{
//一组进行预排
for (int i = j; i < n - gap; i += gap)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (a[end] > tmp)
{
//后移
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
}
每一次从待排序的数据元素中选出最小和最大的一个元素,存放在序列的起始位置和末尾,直到全部待排序的数据元素排完 。
void SelectSort(int* a, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int maxi = begin, mini = begin;
for (int i = begin; i <= end; i++)
{
if (a[maxi] < a[i])
{
maxi = i;
}
if (a[mini] > a[i])
{
mini = i;
}
}
//交换
Swap(&a[mini], &a[begin]);
if (begin == maxi)
{
maxi = mini;
}
Swap(&a[maxi], &a[end]);
end--;
begin++;
}
}
有关堆排序知识,点击跳转 http://t.csdn.cn/cFgIP
void AdjustDown(int* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
child++;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int* a, int n)
{
//建堆
//向下调整
//升序——建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
//交换
Swap(&a[0], &a[end]);
//再向下调整
AdjustDown(a, end, 0);
end--;
}
}
将两个相邻的数据比较然后进行交换,冒一次就会把最小的或最大的那个数冒到最终位置。
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int flag = 0;
for (int j = 0; j < n - i - 1; j++)
{
if (a[j] > a[j + 1])
{
//交换
int tmp = a[j];
a[j] = a[j + 1];
a[j + 1] = tmp;
flag = 1;
}
}
if (!flag)
{
break;
}
}
}
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int begin = left, end = right;
int keyi = left;
while (left < right)
{
while (left < right && a[right] >= a[keyi])
{
right--;
}
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[keyi], &a[left]);
keyi = left;
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int begin = left, end = right;
int key = a[left];
int hole = left;
while (left < right)
{
while (left < right && a[right] >= key)
{
right--;
}
a[hole] = a[right];
hole = right;
while (left < right && a[left] <= key)
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
QuickSort(a, begin, hole - 1);
QuickSort(a, hole + 1, end);
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int begin = left, end = right;
int keyi = left;
int prev = left;
int cur = left + 1;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)
{
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
QuickSort(a, begin, prev - 1);
QuickSort(a, prev + 1, end);
}
//快速排序(三路划分法)针对数组里有大量重复的元素
void QuickSortThreeWay(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
//三数取中
int keyi = GetMidIndex(a, begin, end);
Swap(&a[begin], &a[keyi]);
int left = begin;
int right = end;
int cur = begin + 1;
int key = a[begin];
while (cur <= right)
{
if (a[cur] < key)
{
Swap(&a[cur], &a[left]);
cur++;
left++;
}
else if (a[cur] > key)
{
Swap(&a[cur], &a[right]);
right--;
}
else
{
cur++;
}
}
//分为了三个区间
//[begin, left - 1][left, right][right + 1, end]
QuickSortThreeWay(a, begin, left - 1);
QuickSortThreeWay(a, right + 1, end);
}
使用栈实现快速排序的非递归,每次将要排序的区间范围入栈......
int PartSort1(int* a, int left, int right)
{
int midi = GetMidIndex(a, left, right);
int keyi = left;
Swap(&a[keyi], &a[midi]);
while (left < right)
{
while (left < right && a[right] >= a[keyi])
{
right--;
}
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[keyi], &a[left]);
return left;
}
void QuickSortNonR(int* a, int begin, int end)
{
ST st;
STInit(&st);
STPush(&st, end);
STPush(&st, begin);
while (!STEmpty(&st))
{
int left = STGetTop(&st);
STPop(&st);
int right = STGetTop(&st);
STPop(&st);
int keyi = PartSort1(a, left, right);
//[left, keyi - 1] keyi [keyi + 1, right]
if (keyi + 1 < right)
{
STPush(&st, right);
STPush(&st, keyi + 1);
}
if (left < keyi - 1)
{
STPush(&st, keyi - 1);
STPush(&st, left);
}
}
STDestory(&st);
}
三数取中可以很大程度上避免分组"一边倒"的情况。
//三数取中
int GetMidIndex(int* a, int left, int right)
{
//随机数取法
int mid = left + (rand() % (right - left + 1));
//int mid = (left + right) / 2;
if (a[left] < a[mid])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return right;
}
else
{
return left;
}
}
else
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return left;
}
else
{
return right;
}
}
}
归并排序——是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left == right)
{
return;
}
//小区间优化
if (right - left + 1 < 10)
{
InsertSort(a, right - left + 1);
return;
}
int mid = (left + right) / 2;
//[left, mid][mid + 1, right]
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
//归并
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
//拷贝回去
memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}
//归并排序(递归排序)
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail!\n");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
//归并排序(非递归)
void MergeSortNonR1(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail!\n");
return;
}
//gap —— 两个进行归并的序列各自的个数
int gap = 1;
while (gap < n)
{
int j = 0;
for (int i = 0; i < n; i += 2*gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//无需归并
if (end1 >= n || begin2 >= n)
{
break;
}
//边界修正
if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[j++] = a[begin1++];
}
else
{
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
//归并一段,拷贝一段
memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
}
gap *= 2;
}
free(tmp);
}
小区间优化可以减少87.5%左右的栈帧调用,当此时区间小于10时,可以采用直接插入排序来使区间有序,而不需要继续往下递归调用了。
计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用,步骤如下:
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中
排序部分到此结束,数据结构初阶也告一段落了,接下来会进行C++的学习,也会将学到的不定时的整理成文章发出来!!!