排序:使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
内部排序:数据元素全部放置在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能再内外存之间移动数据的排序。
常见的排序算法:
- 插入排序:
①直接插入排序
②希尔排序 - 选择排序:
①选择排序
②堆排序 - 交换排序:
①冒泡排序
②快速排序 - 归并排序:
归并排序
常见排序的实现:
1. 插入排序:
直接插入排序:
给已经有序的序列中插入新的数据(假设递增)
假设:第一个数据有序
插入过程:从有序序列的最后一个位置向前遍历,找到第一个小于待插入数据的位置,待插入的数据放入已找到位置的下一个位置。
//时间复杂度O(n^2)
//数据有序时:O(n)
void insertSort(int* arr, int n)
{
//假设第一个数据有序
//未插入数据:[1,n]
for (int i = 1; i < n; ++i)
{
//从有序数据的最后一个位置向前遍历
int end = i - 1;
int data = arr[i];
while (end >= 0 && arr[end] > data)
{
//大的数据向后移动
arr[end + 1] = arr[end];
end--;
}
arr[end + 1] = data;
}
}
希尔排序:
让数据越来越接近有序,减少数据移动的次数,调高插入排序的性能。
//时间复杂度:n^1.3
//稳定性:不稳定
void shellSort(int* arr, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
//最后一趟排序,间隔必须是1,保证所有数据都在同一组
//一趟希尔排序
for (int i = gap; i < n; ++i)
{
//同一组数据,最后一个有序数据的位置
int end = i - gap;
//待插入的数据
int data = arr[i];
while (end >= 0 && arr[end] > data)
{
arr[end + gap] = arr[end];
end -= gap;
}
arr[end + gap] = data;
}
}
}
2. 选择排序
选择排序:
递增
①每一次从未排序的数据中找到一个最小的
②把最小的放到未排序的数据的头部,继续执行第一步
void Swap(int* arr, int pos1, int pos2)
{
int tmp = arr[pos1];
arr[pos1] = arr[pos2];
arr[pos2] = tmp;
}
//选择排序
//时间复杂度:O(n^2)
//稳定性:稳定
void selectSort(int* arr, int n)
{
//从未排序的序列中找最值,存放到未排序的起始位置
//未排序的区间:[start,end]
int start = 0;
int end = n - 1;
while (start < end)
{
int minIdx = start;
int i;
for (i = start + 1; i <= end; ++i)
{
if (arr[i] < arr[minIdx])
minIdx = i;
}
//把最小值存开始位置
Swap(arr, start, minIdx);
//剩余未排序的空间[start+1, end]
++start;
}
}
void selectSort2(int* arr, int n)
{
int start = 0;
int end = n - 1;
//每次从未排序的区间找到一个最大值和一个最小值
//最小值放在头部,最大值放在尾部
//遍历的次数减少一半
while (start < end)
{
int minIdx = start;
int maxIdx = start;
for (int i = start + 1; j < +end; ++i)
{
if (arr[i] < arr[minIdx])
minIdx = i;
if (arr[i] > arr[maxIdx])
maxIdx = i;
}
//最小值放在头部
Swap(arr, start, minIdx);
//判断最大值的位置是否在起始位置
if (maxIdx == start)
maxIdx = minIdx;
//最大值放在尾部
Swap(arr, end, maxIdx);
++start;
--end;
}
}
堆排序:
指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。通过堆来进行选择数据。需要注意的是升序要建大堆,排降序建小堆。
递增:
建大堆:从最后一个非叶子节点开始(该节点位置为: (n-2) / 2 ),执行向下调整
交换根节点和最后一个叶子节点的位置,执行向下调整(最后一个叶子节点不进行操作)
//交换函数实现
void Swap(int* arr, int pos1, int pos2)
{
int tmp = arr[pos1];
arr[pos1] = arr[pos2];
arr[pos2] = tmp;
}
//堆排序
//时间复杂度:O(nlog(n))
//稳定性:不稳定
//向下调整操作
void shiftDown(int* arr, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && arr[child + 1] > arr[child])
++child;
if(arr[child] > arr[parent])
{
Swap(arr, child, parent);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
//堆排序实现
void heapSort(int* arr, int n)
{
for (int i = (n - 2) / 2; i >= 0; --i)
{
shiftDown(arr, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(arr, end, 0);
shiftDown(arr, end, 0);
--end;
}
}
3. 交换排序:
冒泡排序:
(假设递增)
相邻元素进行比较,大的元素向后移动
//交换函数实现
void Swap(int* arr, int pos1, int pos2)
{
int tmp = arr[pos1];
arr[pos1] = arr[pos2];
arr[pos2] = tmp;
}
//冒泡排序
//时间复杂度:O(n^2),数据有序O(n)
//稳定性:稳定
void bubbleSort1(int* arr, int n)
{
//相邻元素进行比较
//每一次遍历的范围:0~未排序数据的最后一个位置
int end = n;
while (end > 0)
{
//flag:标记一轮冒泡排序是否发生了交换操作
int flag = 0;
//一轮冒泡排序
for (int i = 1; i < end; ++i)
{
if (arr[i - 1] > arr[i])
{
//大的向后移动
Swap(arr, i - 1, i);
flag = 1;
}
}
//如果没有发生交换,说明剩余元素全部有序
if (!flag)
break;
--end;
}
}
void bubbleSort2(int* arr, int n)
{
//相邻元素进行比较
//每一次遍历的范围:0~未排序数据的最后一个位置
for (int i = 0; i < n-1; i++) //比较的轮数
{
for (int j = 0; j < n-1-i; j++) //每轮比较的次数
{
if (arr[j] > arr[j + 1])
Swap(arr, j, j + 1);
}
}
}
快速排序:
为Hoare于1962年提出的一种二叉树结构的交换排序方法。
基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序如果不做优化容易导致栈的溢出
①horae法:
选取一个基准值,
从剩余元素中开始遍历:
1从后往前找到第一个小于基准值的数据(如递减:从后往前找大的)
2从前往后找到第一个大于基准值的数据(如递减:从前往后找小的)
3交换找到的两个值
4从交换的位置,分别开始,继续执行第一步
结束:用相遇位置的数据和基准值进行交换
//交换函数实现
void Swap(int* arr, int pos1, int pos2)
{
int tmp = arr[pos1];
arr[pos1] = arr[pos2];
arr[pos2] = tmp;
}
//快速排序
//时间复杂度:
//理想情况:划分均匀O:(nlog(n))
//最坏情况:数据有序:O(n^2)
//获取基准值:三数取中法:起始,中间,结束
int getMid(int* arr, int begin, int end)
{
int mid = begin + (end - begin) / 2;
if (arr[begin] > arr[mid])
{
if (arr[mid] > arr[end])
//begin > mid > end
return mid;
else if (arr[begin] > arr[end])
//begin > end > mid
return end;
else
//end > begin > mid
return begin;
}
else
{
//mid > begin
if (arr[mid] < arr[end])
//end > mid > begin
return mid;
else if (arr[begin] < arr[end])
//mid > end > begin
return end;
else
//mid > begin > end
return begin;
}
}
//返回划分之后,基准值所在的位置
int partion(int* arr, int begin, int end)
{
//获取基准值位置
int mid = getMid(arr, begin, end);
//把基准值放到起始位置
Swap(arr, begin, mid);
int key = arr[begin];
int start = begin;
while (begin < end)
{
//从后往前找小于基准值的位置
while (begin < end && arr[end] >= key)
--end;
//从前往后找大于基准值的位置
while (begin < end && arr[begin] <= key)
++begin;
//交换
Swap(arr, begin, end);
}
//交换基准值和相遇位置的数据
Swap(arr, start, begin);
return begin;
}
//快速排序实现
void quickSort(int* arr, int begin, int end)
{
if (begin >= end)
return;
//div:一次划分后,基准值位置
int div = partion(arr, begin, end);
//左右两部分进行快速排序
//[begin,div-1]
//[div+1,end]
quickSort(arr, begin, div - 1);
quickSort(arr, div + 1, end);
}
②填坑法:
//填坑法
int partion2(int* arr, int begin, int end)
{
//获取基准值位置
int mid = getMid(arr, begin, end);
//把基准值放到起始位置
Swap(arr, begin, mid);
//第一个值作为基准值,第一个位置为初始的坑的位置
int key = arr[begin];
int start = begin;
while (begin < end)
{
//从后往前找小于基准值的位置
while (begin < end && arr[end] >= key)
--end;
//填坑
arr[begin] = arr[end];
//从前往后找大于基准值的位置
while (begin < end && arr[begin] <= key)
++begin;
//填坑
arr[end] = arr[begin];
}
arr[begin] = key;
return begin;
}
//快速排序实现
void quickSort(int* arr, int begin, int end)
{
if (begin >= end)
return;
//div:一次划分后,基准值位置
int div = partion2(arr, begin, end);
//左右两部分进行快速排序
//[begin,div-1]
//[div+1,end]
quickSort(arr, begin, div - 1);
quickSort(arr, div + 1, end);
}
③前后指针法:
prev:上一个小于基准值的位置
cur:下一个小于基准值的位置
当cur走到一个小于基准值的位置,则:
判断prev和cur是否连续:
如果连续:区间[prev,cur]的值都是不大于基准值,更新prev,cur
如果不连续:区间[prev,cur]的值,有大于基准值的,更新prev,数据交换,更新cur
//前后指针法
int partion2(int* arr, int begin, int end)
{
//上一个小于基准值的位置
int prev = begin;
//下一个小于基准值的位置
int cur = begin + 1;
int key = arr[begin];
while (cur <= end)
{
//当cur走到下一个小于基准值的位置,判断prev和cur是否连续
if (arr[cur] < key && ++prev != cur)
{
//不连续,交换数据:prev, cur
Swap(arr, prev, cur);
}
++cur;
}
Swap(arr, begin, prev);
return prev;
}
void quickSort(int* arr, int begin, int end)
{
if (begin >= end)
return;
//div:一次划分后,基准值位置
int div = partion3(arr, begin, end);
//左右两部分进行快速排序
//[begin,div-1]
//[div+1,end]
quickSort(arr, begin, div - 1);
quickSort(arr, div + 1, end);
}
4. 归并排序:
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并:合并有序的子序列,产生更大的有序的子序列
分解:把数据按照位置,均衡的分成两个子序列,一直到子序列中只有一个数据
合并:相邻有序的子序列进行合并
排序的过程中:需要O(n)的辅助空间
//*********************************归并排序**************************
//时间复杂度:O(nlog(n))
//空间复杂度O(n)
//稳定性:稳定
//相邻子序列合并: begin end , end+1 end2
//begin mid end2
void merge(int* arr, int begin, int mid, int end, int* tmp)
{
//递增
//子区间:[begin, mid] [mid+1, end]
int begin1 = begin;
int end1 = mid;
int begin2 = mid + 1;
int end2 = end;
//辅助空间的起始位置
int idx = begin;
//合并有序序列
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] <= arr[begin2])
tmp[idx++] = arr[begin1++];
else
tmp[idx++] = arr[begin2++];
}
//判断是否有未合并的元素
if (begin1 <= end1)
memcpy(tmp + idx, arr + begin1, sizeof(int) * (end1 - begin1 + 1));
if (begin2 <= end2)
memcpy(tmp + idx, arr + begin2, sizeof(int) * (end2 - begin2 + 1));
//合并之后的序列拷贝到原始数组的对应区间
memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并方法实现(递归法)
void _mergeSort(int* arr, int begin, int end, int* tmp)
{
if (begin >= end)
return;
int mid = begin + (end - begin) / 2;
//首先划分子序列
_mergeSort(arr, begin, mid, tmp);
_mergeSort(arr, mid + 1, end, tmp);
//合并两个有序的子序列[begin, mid] [mid+1, end]
merge(arr, begin, mid, end, tmp);
}
void mergeSort(int* arr, int n)
{
//申请辅助空间
int* tmp = (int*)malloc(sizeof(int) * n);
_mergeSort(arr, 0, n - 1, tmp);
free(tmp);
}
//归并方法实现(非递归方法)
void mergeSortNoR(int* arr, int n)
{
//申请辅助空间
int* tmp = (int*)malloc(sizeof(int) * n);
int step = 1;
while (step < n)
{
for (int idx = 0; idx < n; idx += 2 * step)
{
//找到两个待合并的子序列区间
//[begin, mid] [mid+1, end]
int begin = idx;
int mid = idx + step - 1;
//判断是否存在第二个序列
if (mid >= n - 1)
//不存在第二个子序列,直接跳过
continue;
int end = idx + 2 * step - 1;
//判断第二个子序列是否越界
if (end > n)
end = n - 1;
merge(arr, begin, mid, end, tmp);
}
//更新步长
step *= 2;
}
}
//*********************************计数排序**************************
void countSort(int* arr, int n)
{
//找到最大和最小值
int max, min;
min = max = arr[0];
for (int i = 1; i < n; ++i)
{
if (arr[i] > max)
max = arr[i];
if (arr[i] < max)
min = arr[i];
}
//计算范围
int range = max - min + 1;
//创建一个计数数组,初始化为0
int* countArr = (int*)calloc(range, sizeof(int));
//计数
for (int i = 0; i < n; ++i)
{
countArr[arr[i] - min]++;
}
//遍历计数数组,排序
int idx = 0;
for (int i = 0; i < range; ++i)
{
while (countArr[i]--)
{
arr[idx++] = i + min;
}
}
}