数据结构的各种排序算法
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小递增或递减的
排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。
排序算法可以分为稳定排序和不稳定排序
比如对一个数组,如果A[i] = A[j],A[i]原来在位置前,排序后A[i]还是要在A[j]位置前,这样的排序叫稳定排序。
下面是几种常见的排序算法:
一、插入类排序
1.插入排序
将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的
排序,时间复杂度为O(n^2)。是稳定的排序方法。
时间复杂度:T(n) = O(n²)。
空间复杂度:S(n) = O(1)。
给定一个乱序的数组,可以从前往后慢慢循环进行插入排序,代码如下:
void InsertionSort(int arr[], int n) {
assert(arr);
int i = 0;
int j = 0;
int tmp = 0;
for (i = 1; i < n; ++i) {
/*
**从待插入数组中取出第一个元素
**i - 1是有序数组中的最后一个元素
*/
tmp = arr[i];
j = i - 1;
/*
** j >= 0是对有序部分的边界进行限制
** tmp < arr[j]是插入判断条件
*/
while (j >= 0 && tmp < arr[j]) {
/*
**向后挪一位
*/
arr[j + 1] = arr[j];
j--;
}
/*
**在这里进行插入,找到了插入位置,下标为j + 1
*/
arr[j + 1] = tmp;
}
}
2.希尔排序
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。
把待排序序列分成若干较小的子序列,然后逐个使用直接插入排序法排序,最后再对一个较为有序的序列进
行一次排序,主要是为了减少移动的次数,提高效率。原理应该就是从无序到渐渐有序,要比直接从无序到
有序移动的次数会少一些。
时间复杂度:O(n ^ 1.5)
空间复杂度:O(1)
希尔排序是非稳定排序算法。
代码如下:
void ShellSort(int arr[], int size) {
assert(arr);
// 1.先生成步长序列,此序列直接按照希尔序列生成
// N / 2, N / 4, N / 8....
int gap = size / 2;
for (; gap > 0; gap /= 2) {
//第一重循环,生成了步长序列
int bound = gap;
for (; bound < size; ++bound) {
//第二重循环,完成所有组的元素的插入
//先处理第一组的第一个元素...
//再处理第二组的第一个元素..
//...
//再处理第一组的第二个元素
int value = arr[bound];
int i = bound;
//第三重循环是对,当前元素在组内进行插入排序
for (; i >= gap; i -= gap) {
if (arr[i - gap] > value) {
arr[i] = arr[i - gap];
}
else {
break;
}
}
arr[i] = value;
}//bound >= size, 第一次分组和插排完成
}//gap <= 0,整个排序结束
}
二、交换类排序
1.冒泡排序
冒泡排序(Bubble Sort),是一种 计算机科学领域的较简单的 排序算法。
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
时间复杂度:T(n) = O(n²)
空间复杂度:S(n) = O(1)
是一种稳定性排序代码如下:
void Swap(int* a, int* b) {
assert(a);
assert(b);
int tmp = *a;
*a = *b;
*b = tmp;
}
/*对一个整型数组进行冒泡排序*/
void BubbleSort(int arr[], int sz) {
assert(arr);
/*
**冒泡排序是一种比较简单的排序算法
**重复的遍历数组,每次比较两个元素,把最大或者最小的冒到最后或者最前面
**这样一直,知道再也没有数需要进行交换,就排序完成
*/
int i = 0;
/*
**这里只需要进行sz-2次的循环,因为最后剩一个元素了就不需要再比较了
*/
for (; i < sz - 1; ++i) {
int j = 0;
for (; j < sz - i - 1; ++j) {
/*
**这里也一样,因为是比较当前元素和下一个元素j + 1
*/
if (arr[j] > arr[j + 1]) {
Swap(&arr[j], &arr[j + 1]);
}//arr[j] > arr[j + 1]
}//第一遍冒泡完成,数组最后一个元素元素是最大的了
}//冒泡完成
}
2.快速排序
冒泡排序一次只能消除一个逆序,为了能一次消除多个逆序,采用快速排序。以一个关键字为轴,从左从
右依次与其进行对比,然后交换,第一趟结束后,可以把序列分为两个子序列,然后再分段进行快速排序,
达到高效。
时间复杂度:平均T(n) = O(n㏒n),最坏O(n²)
空间复杂度:S(n) = O(㏒n)
是不稳定排序
快速排序是一种比较重要的排序方法,我这里写了三种方法:①交换法
找到一个基准值key,然后用left和right分别从左右两边寻找,从左往右找到大于key值的元素,然后
从右往左找到小于key值得元素,最后再把key值和中间的值一交换,这时候,元素序列里,key左边的
是小于key的序列,右边的都是大于key的序列,这样用递归的思想,就可以不断的调用函数来实现快速排序
代码如下:
//////////////////////////////////////////////////
//交换法
//////////////////////////////////////////////////
void Swap2(int* a, int* b) {
assert(a);
assert(b);
int tmp = *a;
*a = *b;
*b = tmp;
}
int Partion(int arr[], int _left, int _right) {
assert(arr);
int left = _left;
int right = _right - 1;
int key = arr[right];
while (left < right) {
/*
**从左往右找到比key值大的元素
*/
while (left < right && arr[left] <= key) {
++left;
}
/*
**从右往左找到比key值小的元素
*/
while (left < right && arr[right] >= key) {
--right;
}
/*
**交换
*/
if (left < right) {
Swap2(&arr[left], &arr[right]);
}
}
/*
**此时left指向的值一定大于key
**如果是以为++left导致循环退出(如果没找到,那么肯定left >= right)
**而right在上一次循环中已经是一个大于key的值了
**如果是因为--right导致循环退出(和上面的情况一样)
**left在上面的循环中已经找到了大于key的值
*/
//所以left指向的值一定大于key
Swap2(&arr[left], &arr[_right - 1]);
return left;
}
void QuickSort1(int arr[], int left, int right) {
assert(arr);
if (right - left <= 1) {
return;
}
/*
**具体思路如下:
**每次都找出一个基准key值,然后把数组分为两半
**左边是小于key的元素,右边是大于key的元素
**再进行递归的处理,直到所有的元素都有序了
*/
int mid = Partion(arr, left, right);
QuickSort1(arr, left, mid);
QuickSort1(arr, mid + 1, right);
}
②挖坑法
挖坑法和交换法很类似,也是找到一个基准值key,然后定义left和right,从左往右找到大于key的值,然后把left
的值放到right的位置,再让right从右往左找到小于key的值,再把这个值放到left的位置,这样到最后,肯定会有
一个位置,把key放进去,这样就跟上一个方法一样了
代码如下:
///////////////////////////////////////
//挖坑法
///////////////////////////////////////
int Partion2(int arr[], int begin, int end) {
assert(arr);
int left = begin;
int right = end - 1;
int key = arr[right];
while (left < right) {
while (left < right && arr[left] <= key) {
++left;
}
if (left < right) {
arr[right--] = arr[left];
}
while (left < right && arr[right] >= key) {
--right;
}
if (left < right) {
arr[left++] = arr[right];
}
}
arr[left] = key;
return left;
}
void QuickSort2(int arr[], int left, int right) {
assert(arr);
if (right - left <= 1) {
return;
}
int mid = Partion2(arr, left, right);
QuickSort2(arr, left, mid);
QuickSort2(arr, mid + 1, right);
}
③前后指针法
找到一个基准值,然后定义两个prev和next,next往前走,遇到小于key的值就停下来,然后再让prev往前走,如果
prev不等于next就交换他俩指向的值,然后最后判断,prev是否小于key,如果小于就一交换,这样,prev左边的值
就是小于它的元素,右边就是大于它的元素
代码如下:
////////////////////////////////////////////
//双指针前移法
////////////////////////////////////////////
int Partion3(int arr[], int begin, int end) {
assert(arr);
int prev = begin;
int next = begin + 1;
int key = begin;
while (next <= end - 1) {
if (arr[next] <= arr[key]) {
++prev;
if (prev != next) {
Swap2(&arr[prev], &arr[next]);
}
}
++next;
}
if (arr[prev] != arr[key]) {
Swap2(&arr[prev], &arr[key]);
}
return prev;
}
void QuickSort3(int arr[], int left, int right) {
assert(arr);
if (right - left <= 1) {
return;
}
/*前闭后开区间*/
int mid = Partion3(arr, left, right);
QuickSort3(arr, left, mid);
QuickSort3(arr, mid + 1, right);
}
用非递归实现:
非递归实现用到了栈,代码如下:
/////////////////////////////////////////
//非递归版本
/////////////////////////////////////////
void QuickSortByLoop(int arr[], int size) {
assert(arr);
if (size <= 1) {
return;
}
int left = 0;
int right = size;
SeqStack seq;
SeqStackInit(&seq);
SeqStackPush(&seq, right);
SeqStackPush(&seq, left);
/*如果当前栈不为空就一直循环*/
while (!SeqStackEmpty(&seq)) {
left = SeqStackTopValue(&seq);
SeqStackPop(&seq);
right = SeqStackTopValue(&seq);
SeqStackPop(&seq);
/*
**判断条件
*/
if (right - left <= 1) {
continue;
}
int mid = Partion3(arr, left, right);
SeqStackPush(&seq, mid);
SeqStackPush(&seq, left);
SeqStackPush(&seq, right);
SeqStackPush(&seq, mid + 1);
}
}
栈的代码在末尾给出
三、选择类排序
每一趟在n – i + 1 ( i = 1,2, … , n - 1)个记录中选取关键字最小的记录作为有序序列中的第i个记录
1.选择排序
从第一个记录开始,通过n – 1次关键字的比较,从n个记录中选出关键字最小的记录,并和第一个记录进行交换。
第二趟从第二个记录开始,选择最小的和第二个记录交换。以此类推,直至全部排序完毕。
时间复杂度:T(n) = O(n²)
空间复杂度:S(n) = O(1)
稳定性:不稳定排序
代码如下:
/*选择排序*/
void SelectSort(int arr[], int sz) {
assert(arr);
int i = 0;
for (; i < sz--;) {
/*每次选择出最大或者最小的元素,然后把这个元素放入数组的最后面*/
int j = 1;
int index = 0;
for (; j < sz + 1; ++j) {
if (arr[j] > arr[index]) {
/*更新最大的元素下标*/
index = j;
}
}
/*交换最大值和当前数组最后一位*/
Swap(&arr[index], &arr[sz]);
}
}
2.堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种 数据结构所设计的一种 排序算法,它是选择排序的一种。
可以利用 数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是 完全二叉树。
大根堆堆顶元素堆中,小根堆堆顶元素是最小的值,每次取出堆顶元素,然后调整堆,然后继续取元素,直到
堆为空,也得到了一个有序元素的序列
时间复杂度:T(n) = O(n㏒n)
空间复杂度:S(n) = O(1)
稳定性:不稳定排序
代码如下:
int Compare(int a, int b) {
return a > b ? 1 : 0;
}
void Swap(int *a, int* b) {
assert(a);
assert(b);
int tmp = *a;
*a = *b;
*b = tmp;
}
void AdjustDown(int arr[], int sz, int index) {
assert(arr);
/*
**把堆中的堆顶个元素往下沉
*/
int parent = index;
/*
**左孩子节点
*/
int child = parent * 2 + 1;
/*
**看孩子节点是否小于sz
*/
while (child < sz) {
/*
**判断child是否越界
**比较左孩子节点和右孩子节点,然后取其中的较小值
*/
if (child + 1 < sz && Compare(arr[child], arr[child + 1])) {
child += 1;
}
/*
**父节点和较小的子节点判断大小,如果大于较小的节点,就交换
*/
if (Compare(arr[parent], arr[child])) {
/*交换*/
Swap(&arr[parent], &arr[child]);
}
else {
/*该数组已经是个堆了*/
break;
}
/*更新父节点的子节点的值*/
parent = child;
child = parent * 2 + 1;
}
}
/*堆排序*/
void HeapSort(int arr[], int sz) {
assert(arr);
//先把数组设置成堆
int index = (sz - 1 - 1) / 2;
while (index) {
AdjustDown(arr, sz, index--);
}
AdjustDown(arr, sz, index);
//然后再把堆首元素取出来放到堆的最后,对堆进行排序
while (sz > 1) {
/*
**每次把堆顶元素和最后一个元素一交换
**然后再对堆进行重新排序
*/
Swap(&arr[0], &arr[sz - 1]);
--sz;
AdjustDown(arr, sz, 0);
}
}
涉及到堆的部分如果不懂可以在我关于堆的博客里看看
四、归并类排序
1.归并排序
假设初始序列右n个记录,首先将这n个记录看成n个有序的子序列,每个子序列的长度为1,然后两两归并,
得到n/2向上取整 个长度为2(n为奇数时,最后一个序列的长度为1)的有序子序列。在此基础上,在对长
度为2的有序子序列进行两两归并,得到若干个长度为4的有序子序列。如此重复,直至得到一个长度为n的
有序序列为止。
时间复杂度:T(n) = O(n㏒n)
空间复杂度:S(n) = O(n)
稳定性:稳定排序
代码如下:
把一个有序数组进行归并:
void _MergeArray(int arr[], int left, int mid, int right, int* tmp) {
assert(arr);
assert(tmp);
int cur1 = left;
int cur2 = mid;
int tmp_index = left;
while (cur1 < mid && cur2 < right) {
/*
**把给定范围的数组分为两半然后进行归并,得到一个有序的数组
*/
if (arr[cur1] < arr[cur2]) {
tmp[tmp_index++] = arr[cur1++];
}
else {
tmp[tmp_index++] = arr[cur2++];
}
}
/*
**把当前的接上
*/
while (cur1 < mid) {
tmp[tmp_index++] = arr[cur1++];
}
while (cur2 < right){
tmp[tmp_index++] = arr[cur2++];
}
memcpy(arr + left, tmp + left, sizeof(int)* (right - left));
}
递归版本归并排序:
/*
**前闭后开区间
*/
void _MergeSort(int arr[], int left, int right, int* tmp) {
assert(arr);
assert(tmp);
if (right - left <= 1) {
return;
}
int mid = left + (right - left) / 2;
_MergeSort(arr, left, mid, tmp);
_MergeSort(arr, mid, right, tmp);
/*
**先保证左右区间都是有序之后才能进行合并
*/
_MergeArray(arr, left, mid, right, tmp);
}
/*
**归并排序
*/
void MergeSort(int arr[], int size) {
assert(arr);
/*
**申请的内从空间是用来暂时保存已经排好序的部分数组,最后重新复制给数组
*/
int* tmp = (int*)malloc(sizeof(arr)* size);
_MergeSort(arr, 0, size, tmp);
free(tmp);
}
非递归版本归并排序:
/*非递归版本的归并排序*/
void MergerSortByLoop(int* arr, int size) {
assert(arr);
if (size <= 1) {
return;
}
int* tmp = (int*)malloc(sizeof(int)* size);
int gap = 1;
for (; gap < size; gap *= 2) {
int i = 0;
for (; i < size; i += 2 * gap) {
int left = i;
int mid = i + gap;
int right = i + 2 * gap;
if (mid > size) {
mid = size;
}
if (right > size) {
right = size;
}
_MergeArray(arr, left, mid, right, tmp);
}
}
free(tmp);
}
上面用到的栈的代码:
SeqStack.h
#pragma once
#include<stdio.h>
#include<stddef.h>
typedef int SeqType;
#define SEQDATAMAX 1000
/*创建一个栈的结构体*/
typedef struct SeqStack{
SeqType data[SEQDATAMAX];
int top;
int bottom;
}SeqStack;
/*初始化栈*/
void SeqStackInit(SeqStack* seq);
/*从栈里面压入一个元素*/
void SeqStackPush(SeqStack* seq, SeqType value);
/*出栈一个元素*/
void SeqStackPop(SeqStack* seq);
/*取栈顶元素*/
SeqType SeqStackTopValue(SeqStack* seq);
/*销毁栈*/
void SeqStackDestory(SeqStack* seq);
/*判断当前栈是不是空栈*/
int SeqStackEmpty(SeqStack* seq);
SeqStack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqStack.h"
/*初始化栈*/
void SeqStackInit(SeqStack* seq) {
seq->top = 0;
seq->bottom = seq->top;
}
/*从栈里面压入一个元素*/
void SeqStackPush(SeqStack* seq, SeqType value) {
if (seq == NULL) {
return;
}
/*判断栈是不是已经满了*/
if (seq->top == SEQDATAMAX - 1) {
return;
}
seq->top++;
seq->data[seq->top] = value;
}
/*删除栈顶元素,出栈一个元素*/
void SeqStackPop(SeqStack* seq) {
if (seq == NULL) {
return;
}
/*判断栈是否为空栈*/
if (seq->top == seq->bottom) {
printf("栈为空");
return;
}
seq->top--;
}
/*取栈顶元素*/
SeqType SeqStackTopValue(SeqStack* seq) {
if (seq == NULL) {
return;
}
return seq->data[seq->top];
}
/*销毁栈*/
void SeqStackDestory(SeqStack* seq) {
if (seq == NULL) {
return;
}
seq->top = 0;
}
/*判断当前栈是不是空栈*/
int SeqStackEmpty(SeqStack* seq) {
if (seq == NULL) {
return;
}
if (seq->top == seq->bottom) {
return 1;
}
return 0;
}