所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
待排序数据在排序前后相同元素的相对位置是否发生改变
如果改变:该排序不稳定
反之就是稳定的
数据元素全部放在内存中的排序。
数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
主要思想
代码实现
//插入排序(升序)
void InsertSort(int array[], int size)
{
for (int i = 1; i < size; i++)//控制循环趟数
{
int key = array[i];//标记待插入元素
int end = i - 1;//end标记已经排好序的序列的最后一个下标
while (end >= 0 && array[end] > key)
{
array[end + 1] = array[end];
end--;
}
array[end + 1] = key;//找到插入元素的位置
}
}
复杂度分析
稳定性 :稳定!!!
插入排序没有出现跨元素交换的情况,因此相同的元素排序前后相对位置不会发生变化
适用场景
元素基本有序 或者 元素数量比较少
主要思想:希尔排序是对直接插入排序的升级
注:基准值如何选取?
采用Kunth提出的gap = gap/3 (向下取整)+1
代码实现
//希尔排序
//定义gap,将数组下标%gap相等的元素分为一组
//对每一组采用插入排序的方式进行排序
//对gap每次执行 gap/3 +1 直到gap减小为1
void ShellSort(int array[],int size)
{
int gap = size;
//gap判断条件应该是大于1,因为gap的值始终是>=1的
while (gap > 1)
{
gap = gap / 3 + 1;//gap等于1时,最后一次排序,排完之后整个数组已经有序
for (int i = gap; i < size; i++)
{
int key = array[i];
int end = i - gap;
while (end>=0 && array[end]>key)
{
array[end + gap] = array[end];
end -= gap;
}
array[end + gap] = key;
}
}
}
复杂度分析
稳定性 :不稳定
存在跨区间交换元素
适用场景
数据比较随机 数据量比较大
主要思想
普通版本的选择排序一次只会从中选出一个元素(最大或最小)
优化后的版本,一次可以从带排序数组中选取出两个元素,分别是最大最小元素
具体步骤如下:
代码实现 原始版本+优化版本
//选择排序
static void Swap(int *left, int *right)
{
int temp = *left;
*left = *right;
*right = temp;
}
void SelectSort(int array[], int size)
{
for (int i = 0; i < size-1; i++)//控制循环趟数 size个元素,需要循环size-1趟
{
int maxPos = 0;
for (int j = 0; j < size - i; j++)//每一趟遍历数组中未排序元素,更新maxPos的位置
{
if (array[maxPos] < array[j])
{
maxPos = j;
}
}
if (maxPos != size - 1 - i)
{
Swap(&array[maxPos], &array[size - 1 - i]);
}
}
}
//优化版本
void SelectSortOP(int array[], int size)
{
int left = 0;
int right = size - 1;
while (left < right)
{
int maxPos = left;
int minPos = left;
int index = left + 1;
while (index <= right)
{
if (array[maxPos] < array[index])
{
maxPos = index;
}
if (array[minPos]>array[index])
{
minPos = index;
}
index++;
}
if (maxPos != right)
{
Swap(&array[maxPos],&array[right]);
}
if (minPos == right)
{
minPos = maxPos;
}
if (minPos != left)
{
Swap(&array[minPos],&array[left]);
}
left++;
right--;
}
}
复杂度分析
稳定性 不稳定
选择排序存在跨区间交换
适用场景
基本情况都可以使用,但都不使用(由于时间复杂度的原因)
主要思想
代码实现
//堆排序
static void HeapAdjust(int array[], int size, int parent)
{
int child = 2 * parent + 1;//默认标记左孩子(完全二叉树,必定是先有左孩子,再有右孩子)
while (child < size)
{
if (child + 1 < size && array[child + 1] > array[child])//更新child,让其指向较大的孩子结点
{
child += 1;
}
if (array[child]>array[parent]) //判断较大孩子结点与父节点的大小关系
{
Swap(&array[child], &array[parent]);
//交换后更新parent与child,因为交换可能会导致子树不符合堆的结构
parent = child;
child = 2 * child + 1;
}
else//满足堆的结构
{
return;
}
}
}
void HeapSort(int array[], int size)
{
//1.建立堆(向下调整) 升序--大堆 降序---小堆
for (int root = (size - 2) / 2; root >= 0; root--)
{
HeapAdjust(array, size, root);
}
//2.利用堆删除的思想进行排序
int end = size - 1;
while (end > 0)
{
Swap(&array[0], &array[end]);
HeapAdjust(array, end, 0);
end--;
}
}
复杂度分析
稳定性 : 不稳定
适用场景 : Top-K问题
主要思想
以升序为例:
给定一组数据,相邻元素之间两两进行比较,一趟下来,将最大的元素排到了数组最后,循环size-1趟后,数组整体将会有序。在每一趟循环过程中,定义一个标志量,用来标记本趟排序是否发生交换,如果发生,说明数组没有完全有序,继续下一趟循环,如果没有发生改变,说明数组已经有序,可以提前跳出循环
代码实现
//冒泡排序
//两两比较,一趟遍历找到一个元素的最终位置(最大或最小)
void BubbleSort(int array[],int size)
{
for (int i = 0; i < size - 1; i++)//控制循环趟数,size个元素循环size-1次即可完成排序
{
int isChanged = 0;//标记一趟遍历中是否有元素交换,若没有交换,说明数组已经有序。此时直接返回,无需再进入下面的循环
for (int j = 0; j < size - 1 - i; j++)//将最大元素搬移至数组最后
{
if (array[j]>array[j + 1])
{
isChanged = 1;
Swap(&array[j], &array[j + 1]);
}
}
if (!isChanged)
{
return;
}
}
}
复杂度分析
稳定性 :稳定
适用场景:数据接近有序
主要思想
代码实现
/递归
void QuickSort(int array[], int left, int right )
{
if (right - left > 1)
{
//根据Partion对数组进行划分--->小于div的位于div的左侧,大于等于div的位于div的右侧
//主要划分方式有 hoare版本 挖坑法 前后指针法
int div = Partion(array, left, right);
QuickSort(array, left, div);
QuickSort(array, div + 1, right);
}
}
上述说法中有两个点值得我们深究:
hoare版本
挖坑法
前后指针法
hoare版本
代码展示
//hoare版本
static int Partion1(int array[], int left, int right)
{
int begin = left;
int end = right - 1;
int mid = GetMidPos(array, left, right);
if (mid != end)
{
Swap(&array[end], &array[mid]);
}
int key = array[end];
while (begin < end)
{
while (begin < end&& array[begin] <= key)
{
begin++;
}
while (begin<end && array[end]>= key)
{
end--;
}
if (begin < end)
{
Swap(&array[begin], &array[end]);
}
}
if (begin != right-1)
{
Swap(&array[begin], &array[right-1]);
}
return begin;
}
挖坑法
主要思想与hoare版本相似
前提:将基准值保存到key中,此时数组基准值所在位置相当于可以占的“坑位”,用end标记
代码展示
//挖坑法
static int Partion2(int array[], int left, int right)
{
int begin = left;
int end = right - 1;
int mid = GetMidPos(array, left, right);
if (mid != end)
{
Swap(&array[end], &array[mid]);
}
int key = array[end];
while (begin<end)
{
while (begin < end && array[begin] <= key)
{
begin++;
}
if (begin < end)
{
array[end] = array[begin];
end--;
}
while (begin < end && array[end] >= key)
{
end--;
}
if (begin < end)
{
array[begin] = array[end];
begin++;
}
}
array[begin] = key;
return begin;
}
前后指针法
//前后指针法
static int Partion(int array[], int left, int right)
{
int cur = left;
int prev = cur - 1;
int mid = GetMidPos(array, left, right);
if (mid != right - 1)
{
Swap(&array[mid], &array[right - 1]);
}
int key = array[right - 1];
while (cur < right)
{
if (array[cur] < key && ++prev != cur)
{
Swap(&array[cur], &array[prev]);
}
cur++;
}
if (++prev != right - 1)
{
Swap(&array[prev], &array[right - 1]);
}
return prev;
}
注:一般情况下找到的基准值要是不在数组末尾,将基准值与末尾元素交换(确保基准值始终在数组的末尾)
//三数取中法确定基准值的下标
static int GetMidPos(int array[], int left, int right)
{
//返回左右端点与中间三个元素中值在中间的元素的下标
int mid = left + ((right - left) >> 1);
if (array[left] < array[right - 1])
{
if (array[left] <= array[mid])
{
return mid;
}
else if (array[mid] >= array[right - 1])
{
return right - 1;
}
else
{
return left;
}
}
else //array[left]>=array[right - 1]
{
if (array[left] < array[mid])
{
return left;
}
else if (array[right - 1]>array[mid])
{
return right - 1;
}
else
{
return mid;
}
}
}
主要思想
代码实现
#include "Stack.h"
//非递归
void QuickSortNor(int array[], int size)
{
int begin = 0;
int end = size;
Stack s;
StackInit(&s);
StackPush(&s, end);
StackPush(&s, begin);
while (!StackEmpty(&s))
{
begin = StackTop(&s);
StackPop(&s);
end = StackTop(&s);
StackPop(&s);
if (end - begin > 1)
{
//div将数组分为左右两部分
int div = Partion(array, begin, end);
//按照先处理左边后处理右边的思想将边界下标入栈
//入栈顺序为先入右后入左
StackPush(&s, end);
StackPush(&s, div + 1);
StackPush(&s, div);
StackPush(&s, begin);
}
}
//栈为空,表示排序完成,将栈销毁
StackDestroy(&s);
}
复杂度分析
稳定性 : 不稳定
适用场景:接近有序 | 求数组中第k大(小)的数据
主要思想
代码实现
//归并排序
static void MergeData(int array[], int left, int mid, int right, int temp[])
{
int begin1 = left;
int end1 = mid;
int begin2 = mid;
int end2 = right;
int index = left;
while (begin1 < end1 && begin2 < end2)
{
if (array[begin1] <= array[begin2])
{
temp[index++] = array[begin1++];
}
else
{
temp[index++] = array[begin2++];
}
}
while (begin1 < end1)
{
temp[index++] = array[begin1++];
}
while (begin2 < end2)
{
temp[index++] = array[begin2++];
}
}
static void _MergeSort(int array[], int left, int right,int temp[])
{
int mid = left + ((right - left) >> 1);
if (right - left > 1)
{
//分解
_MergeSort(array, left, mid,temp);
_MergeSort(array, mid, right,temp);
//合并
MergeData(array,left,mid,right,temp);
memcpy(array + left, temp + left, (right - left)*sizeof(array[0]));
}
}
//递归
void MergeSort(int array[], int size)
{
int* temp = (int*)malloc(sizeof(int)*size);
if (NULL == temp)
{
assert(0);
return;
}
_MergeSort(array, 0, size, temp);
free(temp);
}
主要思想
代码实现
//非递归
void MergeSortNor(int array[], int size)
{
int* temp = (int*)malloc(sizeof(array[0])*size);
if (NULL == temp)
{
assert(0);
return;
}
int gap = 1;
while (gap < size)
{
for (int i = 0; i < size; i+= 2*gap)
{
int left = i;
int mid = left + gap;
int right = mid + gap;
//mid 与right可能会越界,需要进行合法性检验
if (mid > size)
{
mid = size;
}
if (right > size)
{
right = size;
}
//默认从一个元素为一组进行归并
MergeData(array, left, mid, right, temp);
}
//将临时数组temp中的元素copy至array数组
memcpy(array, temp, sizeof(array[0])*size);
gap *= 2;
}
free(temp);
}
复杂度分析
稳定性:稳定
适用场景 :数据量大,无法一次加载到内存 外部排序
// 计数排序
void CountSort(int* array, int size)
{
//1、确定辅助数组大小
int maxValue = array[0];
int minValue = array[0];
for (int i = 1; i < size; i++)
{
if (array[i]>maxValue)
{
maxValue = array[i];
}
if (array[i] < minValue)
{
minValue = array[i];
}
}
int len = maxValue - minValue + 1;
int *count = (int*)malloc(sizeof(int)*len);
if (NULL == count)
{
assert(0);
return;
}
memset(count, 0, sizeof(int)*len);
//2、遍历待排序数组,统计每个元素出现的次数
for (int i = 0; i < size; i++)
{
count[array[i] - minValue]++;
}
//3、遍历辅助数组,按照数组值输出对应个数的元素
int index = 0;
for (int i = 0; i < len; i++)
{
while (count[i]--)
{
//printf("%d ", i + minValue);
array[index++] = i + minValue;
}
}
printf("\n");
//4、释放辅助空间
free(count);
}