排序
概念
将一组杂乱无章的数据按照一定的规律组织起来。排序分内部排序和外部排序。
如果整个排序过程不需要访问外存便能完成,则称此类排序为内部排序。若参加排序的记录数量很大。
整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。
常见的排序算法
插入排序
直接插入排序
原理:
每一次将一个待排序的元素,按其关键字的大小插入到已经排好序的一组元素的适当位置上,直到所有待排序元素全部插入为止。
首先我们给出一组数组
我们默认第一个元素已经是有序的一个序列,可以从第二个元素开始比较寻找插入的位置。
从要插入的元素开始向前遍历,如果前面的元素大于要插入的元素的话,则将此元素向后挪一位并用要插入元素替换。
实现:
void InsertSort(int *array, int size)
{ //升序
int index;
for (int i = 1; i < size; i++) //忽略第一个元素
{
index = array[i];
for (int j = i - 1; j >= 0; j--)
{
if (array[j] > index) // 向后挪一位
{
array[j + 1] = array[j];
array[j] = index;
}
else
break;
}
}
}
二分插入排序
原理:二分插入排序,是利用二分法查找要插入的位置,当找到插入位置时,将插入位置到要插入元素的位置之间的元素向后挪动一位,然后将元素插入到要插入的位置。
实现:
int BinarySearch(int *array,int left,int right,int key) //二分查询
{
int mid;
while (left <= right)
{
mid = (left + right) / 2;
if (array[mid] < key)
left = mid + 1;
else if (array[mid] > key)
right = mid - 1;
}
return left;
}
void BinaryInsert(int *array, int size) // 二分插入
{
int index;
int key;
for (int i = 1; i < size; i++) // 同样默认第一个元素有序
{
key = array[i];
index = BinarySearch(array, 0, i - 1, array[i]);
for (int j = i - 1; j >= index; j--)
{
array[j + 1] = array[j];
}
array[index] = key;
}
}
希尔排序
希尔排序是插入排序的一种又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本。
原理:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
实现:
void Sort(int *array, int size, int gap, int index)
{
for (int i = index + gap; i < size; i += gap) //默认第一个元素已序
{
int key = array[i];
for (int j = i - gap; j >= 0; j-=gap) // 向前遍历
{
if (array[j] > key) // 后移
{
array[j + gap] = array[j];
array[j] = key;
}
}
}
}
void ShellSort(int *array,int size)
{
int gap = 3; //起始分组
while (gap)
{
for (int i = 0; i < gap; i++)
{
Sort(array, size, gap, i);
}
gap--;
}
}
选择排序
原理:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
直接选择排序
在元素集合中寻找关键码最小的元素,放到起始位置,起始位置后移,然后再寻找剩下的元素的最小元素,直到剩余元素只剩下一个的时候,结束。
实现:
void SelectSort(int *array, int size)
{
int min;
for (int i = 0; i < size; i++)
{
min = i;
for (int j = i + 1; j < size; j++)
{
if (array[j] < array[min])
min = j;
}
swap(array[i], array[min]);
}
}
可以对选择排序进行优化,同时选取元素序列中的最大值和最小值。从两端向中间进行排序。
void SelectSortOP(int *array, int size)
{ // 选择插入
int max;
int min;
int num = size / 2;
for (int i = 0; i < num; i++)
{
max = i;
min = i;
for (int j = i + 1; j < size - i; j++)
{
if (array[j] > array[max])
max = j;
else if (array[j] < array[min])
min = j;
}
swap(array[i], array[min]);
swap(array[size -1 - i], array[max]);
}
}
堆排序
堆排序是利用堆积树这种数据结构所设计的一种排序算法,利用了大堆(小堆)堆顶记录的关键字最大(最小)的特点。
首先需要建立一个大堆或小堆。
交换堆顶元素与最后一个元素,然后再对交换后的堆进行调整,再调整出一个大堆(小堆)。
建堆完成之后,交换元素并继续进行调整,在堆中已经详细解析过了。
首先:
void Adjust(int *array, int size, int root) //向下调整
{
int parent = root;
int child = parent * 2 + 1;
while (child < size)
{
if ((child + 1) < size&&array[child + 1] > array[child])// 左右子树中较大的
child++;
if (array[parent] < array[child])
{
swap(array[parent], array[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void HeapSort(int *array, int size)
{
// 建堆
int index = (size - 1) / 2; // 从最后一个非页子节点开始
for (int i = index; i >= 0; i--)
{
Adjust(array, size, i);
}
//排序
for (int i = 0; i < size; i++)
{
swap(array[0], array[size - 1 - i]);
Adjust(array, size - 1 - i, 0);
}
}
交换排序
冒泡排序
原理:
一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
实现:
void BubbleSort(int *array, int size)
{
int index;
for (int i = 0; i < size; i++)
{
index = 0;
for (int j = 0; j < size - 1 - i; j++)
{
if (array[j] > array[j + 1])
{
swap(array[j + 1], array[j]);
index++;
}
}
if (index == 0)
return;
}
}
快速排序
原理:
以待排序列中的一个元素作为基准值,其他元素分为两部分,一部分全部都小于该基准值,另一部分全部都大于该基准值。然后再将分开的这两部分分别进行快速排序。
在这里我将最后一个元素作为基准值,并使用双指针的方式进行快速排序。
第一次排序完成之后,按照基准值将其分为左右两部分,继续进行快速排序。
实现:
void QuickSort(int *array, int begin, int end)
{
if (begin < end)
{
int key = array[end];
int cur = begin;
int last = end;
while (cur < last)
{ // 比较交换
if (array[cur] > key && --last != cur)
{
swap(array[cur], array[last]);
}
else
cur++;
}
if (last != end) // 可能会越界/栈溢出
swap(array[last], array[end]);
QuickSort(array, begin, last - 1); //分块快排
QuickSort(array, last + 1, end);
}
}
归并排序
归并排序
原理:
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
类似于合并两条有序链表。
要想归并,先要划分为若干个已序的段,然后再将这些段进行归并排序。
因为一个元素肯定是已序的,所以我们可以直接认为将数组划分为数组大小个数的段,然后将这些段进行合并。
实现:
void MergeSortOP(int *array, int size) // 直接进行合并
{
int gap = 1; // 首次默认分组为1个元素一组
while (gap < size) //可能会溢出
{
for (int i = 0; i < size; i += 2*gap) //跳过的区间
{
int left = i;
int mid = left + gap;
int right = left + 2 * gap;
if (mid > size)
mid = size;
if (right > size)
right = size;
Merge(array, left, mid, right);
}
gap *= 2;
}
}