一些比较常用的排序算法(C语言)
- 插入排序
- 选择排序
- 归并排序
- 堆排序
- 计数排序
- 快速排序
插入排序
- 算法分析
- 从第2个数开始,与之前第一个数比较,如果小于第一个数,那么将第一个数后移,同时设置临时变量保存第二个数,将第一个数设置为第二个数
- 依次类推,当比较到arr[i]时,那么将arr[i]与arr[i-1]个数相比较,如果第i个数小于第i-1个数, 那么将第i-1个数后移,同时,与第i-2个数想比较…
- 如果第arr[i-1]个数>arr[i-2]个数,跳出循环,同时将保存的第i个数插入到i-2的位置
-
算法演示
-
算法实现(带主函数)
void InsertSort(int numbers[],int length)
{
int p; //保存当前的位置
int tempValue; //保存当前变量
for (int i = 1;i < length ;i++)
{
tempValue = numbers[i]; //从第二个元素开始
p = i - 1; //标记上一个元素的位置
while (p >= 0 && tempValue < numbers[p]) {
//与上一个数比较,如果上一个数>当前要比较的数,将上一个数后移
numbers[p + 1] = numbers[p];
p --; //再与上一个数的上一个数进行比较
}
numbers[p+1] = tempValue; //如果上一个数<当前要比较的数,则将当前数插入到相应的位置
}
}
主函数的实现
/*
随机生成一组数,测试使用,当然可以自己手动构建一组数进行测试
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void initARandom(int nums[],int length)
{
srand((unsigned)time(NULL));
for (int i = 0; i < length; ++i) {
nums[i] = rand()%60;
}
}
/*
打印数组中的值,方便测试
*/
void print_array(int A[],int length,char *print_string){
printf("%s\n", print_string);
for (int i = 0; i < length ; ++i){
printf("%d\t",A[i]);
}
printf("\n");
}
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
InsertSort(numbers, length);
print_array(numbers,length,"after sort:");
return 0;
}
输出结果:
before sort:
38 15 54 18 35 31 51 34 46
after sort:
15 18 31 34 35 38 46 51 54
Program ended with exit code: 0
- 算法时间与空间复杂度:
最好情况:O(n)
最坏情况:O(n^2)
平均:O(n^2)
空间复杂度:O(1)
是一种稳定的排序算法
选择排序
- 算法描述
- 查找每组数中的最大值,与最后一位数交换
- 依次递减,再次查找最大值,与倒数第二个交换
- …直到交换到第一个数字,循环结束
- 算法演示
- 算法实现:
/*
查找最大值,返回index
*/
int findMax(int arr[],int length)
{
int max = arr[0];
int index = 0;
for (int i = 0; i < length; i++) {
if (max < arr[i])
{
max = arr[i];
index = i;
}
}
return index;
}
void selection_sort(int arr[],int length)
{
int maxPos;
while (length > 1)
{
maxPos = findMax(arr, length);
int temp = arr[length - 1];
arr[length - 1] = arr[maxPos];
arr[maxPos] = temp;
length--;
}
}
主函数中的实现
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
selection_sort(numbers, length);
print_array(numbers,length,"after sort:");
return 0;
}
测试结果
before sort:
25 16 10 52 51 21 7 59 49
after sort:
7 10 16 21 25 49 51 52 59
Program ended with exit code: 0
- 算法时间与空间复杂度:
最好情况:O(n^2)
最坏情况:O(n^2)
平均:O(n^2)
空间复杂度:O(1)
是不稳定排序算法
归并排序
-
算法分析
主要分两段:
第一段是合并算法
第二段是分治思想递归分解每一段,对每一小段进行排序
第一段合并算法:假设我们已经有了一个分为两段排好序的数组(例如:[6, 7, 8, 9,0,1,2,3,4])- 首先定义三个指针变量,
i
,j
k
,i指针指向第一段[6,7,8,9]中的第一个首元素,j指向第二段[0,1,2,3,4]中的0,k指针指向要输出的数组首地址,[6,7,8,9,0,1,2,3,4]中的6 - 通过比较A[i] 与 B[j]的大小,如果A[i] < B[j],那么,k指向A[i],i++,k++,反之,j++,k++,如果i 与 j 其中的一个已经达到了A 或者B的长度减一,那么跳出循环,将另外一个数组中的元素输出到k所指向的数组中,完成合并
第二段:产生有序的两段数组,为步骤1,步骤2做准备
- 产生一个分两段排好序的数组,用递归操作,首先将数组分为两段,A数组和B数组,再对A数组分段,对B数组分段,直到分段数组中左右两段元素个数都为1个,跳出递归
- 调用上述合并算法,将左右两段进行合并,完成排序
*算法演示
- 算法实现:
合并算法:
- 首先定义三个指针变量,
/*
leftStart:表示数组开始点
rightEnd:表示数组结束点
m:表示要将数组分割的分割点
*/
void merge(int array[],int leftStart,int m,int rightEnd)
{
int LEFT_SIZE = m - leftStart; //计算左边数组的长度
int RIGHT_SIZE = rightEnd - m + 1; //计算右边数组的长度
int leftArray[LEFT_SIZE]; //初始化分段左边的数组
int rightArray[RIGHT_SIZE];//初始化分段右边的数组
//1.将传入的数组中前leftSize个元素分别输出的leftArray数组中
for (int i = leftStart ; i < m; i++)
{
leftArray[i - leftStart] = array[i];
}
//2.将m - rightEnd之间的数据输出到rightArray数组中
for (int i = m; i <= rightEnd; i++)
{
rightArray[i - m] = array[i];
}
int i = 0,j = 0,k = leftStart;
//将两个数组内容合并,假如leftArray 或者 rightArray中某一个数组已经达到了它的长度,那么跳出循环
while (i < LEFT_SIZE && j < RIGHT_SIZE)
{
if (leftArray[i] < rightArray[j])
{
array[k] = leftArray[i];
i++;
k++;
}else
{
array[k] = rightArray[j];
j++;
k++;
}
}
//如果是rightArray达到了它的长度,则将leftArray剩余的数据输出到输出的数组
while (i < LEFT_SIZE)
{
array[k] = leftArray[i];
k++;
i++;
}
//如果是leftArray达到了它的长度,则将rightArray剩余的数据输出到输出的数组
while (j < RIGHT_SIZE)
{
array[k] = rightArray[j];
k++;
j++;
}
}
分段算法:
//分治法思想,切分乱序数组
void mergeSort(int array[],int leftStart,int rightEnd)
{
if (leftStart == rightEnd) return; else {
int m = (leftStart + rightEnd) / 2;//找出中心位置
mergeSort(array, leftStart, m);//切分左边数组
mergeSort(array, m + 1, rightEnd);//切分右边数组
merge(array, leftStart, m + 1, rightEnd);//合并左右两边的数组
}
}
主函数中的实现:
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
mergeSort(numbers, 0, length - 1);
print_array(numbers,length,"after sort:");
return 0;
}
实现结果:
before sort:
14 49 50 50 11 13 59 51 56
after sort:
11 13 14 49 50 50 51 56 59
Program ended with exit code: 0
- 算法时间复杂度与空间复杂度
最好情况:O(nlog2n)
最坏情况:O(nlog2n)
平均:O(nlog2n)
空间复杂度:O(n)
是一种稳定的排序算法
堆排序
-
算法分析:
首先要了解一下二叉树的概念:- 每一个堆就是一个完全二叉树,堆中的每一个根节点都小于两个孩子子结点称为小顶堆,每一个根节点大于两个孩子结点称为大顶堆
- 已知第i个孩子结点,那么父结点为
(i- 1)/2 向下取整
- 已知一个父亲结点i:那么左孩子结点为:
2i + 1
,右孩子结点为2i+2
- 最后一个叶子结点为:
n - 1
,n为结点个数 - 升序排序,要找大顶堆
- 降序排序,要找小顶堆
过程:
- 升序排序,创建大根堆
- 将大根堆的根节点与最后一个叶子结点进行交换
- 交换后将最后一个结点剔除(并不是物理剔除),重新构建大根堆(不包含最后一个最大的叶子结点),直到下次构建大根堆的时候当前结点已经大于n,循环结束,排序完成
- 算法演示
- 算法实现:
/*
交换数组中两个元素之间的顺序
*/
void swap(int arr[],int i, int j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/*
除该节点外,子结点均为堆时,可以直接调用此方法,此方法是堆排序的最小化单元
*/
void heapify(int tree[],int n,int i)
{
if (i >= n) //递归出口
{
return;
}
int left = 2 * i + 1; //第i个结点的左结点
int right = 2 * i + 2; //第i个结点的右结点
int max = i;
if (left < n && (tree[max] < tree[left]))
{ //当左结点不是叶子结点的时候(因为叶子结点没有子结点了)
max = left;
}
if (right < n && (tree[max] < tree[right]))
{
max = right;
}
//以上两个判断是找最大值,如果最大值不是当前结点,交换使得当前结点最大
if (max != i)
{
swap(tree, max, i);
//递归当前结点,当前结点达到跳出递归的条件,也就是到了叶子结点,跳出递归
heapify(tree, n, max);
}
}
以上heapify
算法是除了当前传入的结点,子结点已经是堆了,才会调用该方法,那么如果是一个乱序的数组,则需要首先创建大根堆
void build_heap(int tree[],int n)
{
int last_node = n - 1;//找到最后一个叶子结点,自下而上的将所有父亲结点都变为大根堆
int parent = (last_node - 1) / 2;//找到父亲结点
for (int i = parent; i >= 0; i--)
{
heapify(tree, n, i);将每一个结点都变为大根堆
}
}
排序实现算法
void heap_short(int tree[],int n)
{
build_heap(tree,n); //首先要创建一个大根堆
int last_node = n - 1; //找到最后一个叶子结点
for (int i = last_node;i >= 0; i--)
{
swap(tree, i, 0);//交换根节点与叶子结点,交换后,根节点再是大根堆结点,但是除了最后一个结点,其他结点的子结点均为大根堆结点,所以需要对根节点重新做一次heapify
heapify(tree, i, 0);
}
}
主函数中的实现:
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
heap_short(numbers, length);
print_array(numbers,length,"after sort:");
return 0;
}
before sort:
10 47 40 30 51 37 13 25 23
after sort:
10 13 23 25 30 37 40 47 51
Program ended with exit code: 0
- 算法时间与空间复杂度:
最好情况:O(nlog2n)
最坏情况: O(nlog2n)
平均:O(nlog2n)
空间复杂度:O(1)
计数排序
-
算法分析:
- 首先,需要一个辅助数组cArray,用于存放计数的数目,还需要一个bArray,用于存放排好序的数据
- 获取原数据数组中的最大值,已确定辅助数组cArray的长度,长度为最大值+1
- 将原数组中第i个元素出现的次数放入到辅助数组cArray对应的下标,例如 原数组中的数据为3,那么就将1放入cArray下标为3的位置cArray[3]=1
- 如果有重复,那么就将该位置计数器加1,重复几次,就加几,比如3出现了4次,那么cArray[3]=4
- 将原数组中的元素逐个输出到bArray中,没输出一次,cArray对应的数据就要减1,比如已经输出了3,那么 cArray[3] = 4-1
-
算法演示:
-
算法实现:
//首先要获取数组的最大值
int getMax(int nums[],int length)
{
int max = nums[0];
for (int i = 1; i < length; i++) {
if (nums[i] > max)
{
max = nums[i];
}
}
return max;
}
//计数方法
void counting(int numbers[],int bArray[],int k,int length)
{
//找出最大值,确认在计数排序数组中的位置
int cArray[k + 1];
for (int i = 0; i < k + 1; i++)
{
cArray[i] = 0;
}
//计数排序核心,记录每个数据出现的次数,在cArray中对应的位置做+1操作
for (int i = 0; i < length; i++) {
cArray[numbers[i]] += 1;
}
//将cArray中每一项与前一项做加法,得到i(这里的i其实是原数组中的元素) 元素在即将要输出数组中的位置
for (int i = 1 ;i < k + 1; i++)
{
cArray[i] += cArray[i - 1];
}
//输出到B数组
for (int i = 0; i < length ; i++)
{
//输出一次,那么久相应的要在c数组中对应的位置减一
cArray[numbers[i]] --;
bArray[cArray[numbers[i]]] = numbers[i];
}
numbers
}
//排序的实现
void counting_sort(int array[],int bArray[],int length)
{
int max = getMax(array, length);
counting(array, bArray, max, length);
}
在主函数中的实现:
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
int bArray[length];
counting_sort(numbers,bArray, length);
print_array(bArray,length,"after sort:");
return 0;
- 输出结果
before sort:
6 21 14 32 2 21 33 34 25
after sort:
2 6 14 21 21 25 32 33 34
Program ended with exit code: 0
- 算法时间与空间复杂度分析:
最好情况:O(n + k)
最坏情况:O(n + k)
平均:O(n + k)
空间复杂度:O(n+k)
计数排序是一个稳定的排序
快速排序
- 算法分析:
在快排中,还是用用到分治的思想,将问题最小化到一个子问题,然后通过递归到全部问题。- 首先,我们要找一个basicKey,也叫pivotKey,作为左右指针比较的基准值,从最右边的指针开始,依次向左移动,直到出现一个比pivotKey小的值,将a[right]赋值给a[left],然后移动左指针left,找到一个比pivokey大的值,将a[left]赋值给a[right],循环直到left=right,此时返回left与right所处的值为整个数组的中心点
- 将pivokey赋值给a[left]
- 以返回的枢值为中心,分别对左右两个子数组进行步骤一,直到数组中的元素为1,此时,跳出递归
- 整个循环结束,排序完成
- 算法演示图:
- 算法实现过程:
//返回枢值(中心值,也就是left与right指针重合的地方)
int partition(int array[],int left,int right)
{
int key = array[left];// 将第1个作为关键字
while (left < right) {// 两端交替向中间扫描
// 移动最右边的指针,找到比pivotKey值小的值
while (left < right && array[right] >= key) --right;
array[left] = array[right];// 将比关键字小的移动到左边
// 移动左指针,找到比pivotKey大的值
while (left < right && array[left] <= key) ++left;
array[right] = array[left];// 将比关键字大的移动到右边
}
array[left] = key;// 此时left就是一趟快速排序后的关键字所在的位置
return left;
}
//排序算法
void quickShort(int array[],int left,int right)
{
if (left < right)
{
int mid = partition(array,left,right);
quickShort(array, left, mid - 1); //递归对左子数组排序
quickShort(array, mid + 1, right);//递归对右子数组排序
}
}
主函数中的实现
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
quickShort(numbers, 0, length - 1);
print_array(numbers,length,"after sort:");
return 0;
}
- 输出结果:
int main(int argc, char const *argv[])
{
int length = 9;
int numbers[length];
initARandom(numbers, length);
print_array(numbers, length, "before sort:");
quickShort(numbers, 0, length - 1);
print_array(numbers,length,"after sort:");
return 0;
}
- 时间复杂度与空间复杂度:
最好:O(nlog2n)
最坏:O(n^2)
平均:O(nlog2n)
空间复杂度:O(nlog2n)
快速排序是不稳定排序