C语言8大经典排序算法(详细测试核心代码)

排序算法小结

定义

排序算法的核心是比较和移动,排序算法分类为内部排序和外部排序,区别的要点是排序过程是否需要外部的内存交换过程;也可以按照算法的思路分为比较排序和非比较排序;
排序算法的稳定性,若排序对象中存在多个关键字相同的记录,经过排序后,相同关键字的记录之间的相对次序保持不变,则该排序方法是稳定的,若次序发生变化(哪怕只有两条记录之间),则该排序方法是不稳定的;
时间复杂度,一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得T(n)/f(n)的极限值(当n趋近于无穷大时)为不等于零的常数,记T(n)=O(f(n))T(n),O(f(n))​为算法的渐进时间复杂度,简称时间复杂度;
空间复杂度,空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,它是问题规模n
n的函数,记做S(n)=O(f(n))。比如直接插入排序的时间复杂度是O(n2),空间复杂度是O(1)。而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息,需要辅助空间的大小随着n的增大线性增大。

1、冒泡排序
基本原理:
N个元素,只剩一个首元素的时候,不需要比较,总共需要循环比对N-1次,每一次选出其中最大或者最小的数冒出到尾端;
在每一个循环 i 中,从当前循环的第一元素开始比较到最后一个,因此共有N-1-i次内部循环,内部进行比较操作,符合条件的两数,则移动双方位置。
改进:
1、如果某次冒泡不存在数据交换,则说明已经排序好了,可以直接退出排序
2、头尾进行冒泡,每次把最大的沉底,最小的浮上去,两边往中间靠1

~~鸡尾酒排序与冒泡排序~~ 的不同处在于排序时是以首尾双向在序列中进行排序。
先对数组从左到右进行升序的冒泡排序;
再对数组进行从右到左的降序的冒泡排序;
以此类推,持续的、依次的改变冒泡的方向,并不断缩小没有排序的数组范围。

2、直接插入排序
基本原理:
将第1和第2元素排好序,然后将第3个元素插入到已经排好序的元素中,依次类推,直到轮询到最后一个元素。

    for (int i = 1; i < n; i++)//从第二个元素开始,依次与前一个元素比大小
    {
        if (a[i] < a[i - 1])//若前面的元素大于后面的元素,则寻找后面元素本应正确的位置
        {
            temp = a[i];
            for (j = i - 1; j >= 0 && a[j] > temp; j--)
            {
                a[j + 1] = a[j];
            }
            a[j + 1] = temp;//将无序元素放入到正确的位置
        }
    }

3、希尔排序
基本原理:
插入排序每次只能操作一个元素,效率低。元素个数N,取奇数k=N/2,将下标差值为k的数分为一组(一组元素个数看总元素个数决定),在组内构成有序序列,再取k=k/2,将下标差值为k的数分为一组,构成有序序列,直到k=1,然后再进行直接插入排序。
设置一个最小增量dk,刚开始dk设置为n/2。进行插入排序,随后再让dk=dk/2,再进行插入排序,直到dk为1时完成最后一次插入排序,此时数组完成排序。
例如,现在要对序列{ 1,8,3,9,5,7 }进行希尔排序

第一步,选择增量为3,进行排序:

则产生三个分组(括号为数组元素下标)
{ 1(0),9(3) }
{ 8(1),5(4) }
{ 3(2),7(5) }
然后对该所有分组进行直接插入排序,得到新的分组(注意:此时下标是不会变的,只是用直接插入排序进行数组排序)
{ 1(0),9(3) }
{ 5(1),8(4) }
{ 3(2),7(5) }
因此按照下标,我们可以写出第一趟按照增量为3的希尔排序得到的序列:{ 1,5,3,9,8,7 }

第二步,选择增量为1,进行排序

这一步,直接按照直接插入排序的方法,进行直接插入排序,得到有序序列:{ 1,3,5,7,8,9 }

//    初始时从dk开始增长,每次比较步长为dk
void Insrtsort(int *a, int n,int dk) {
    for (int i = dk; i < n; ++i) {
        int temp = a[i];
        for(j = i-dk; j >=0 && a[j] > temp; j=j-dk)
        {
        	a[j+dk] = a]j];
		}
            a[j+dk] = tmp;         //    插入tmp
        }
    }
}

void ShellSort(int *a, int n) {
    int dk = n / 2;        //    设置初始dk
    while (dk >= 1) {
        Insrtsort(a, n, dk);
        dk /= 2;
    }
}

4、简单选择排序
基本原理:
每次循环选出当前未排序的列表中最小的数,因此每次排序完后的,分为有序组和无序组,无序组的容量会逐渐减少,外部循环N-1次。每一次循环内,并不是比较大小出现冲突就换,而是记录下min的下标位置,一次循环完后才移动一次位置。

   for (int i = 0; i < n; i++)
    {
        int key = i;    //    临时变量用于存放数组最小值的位置
        for (int j = i + 1; j < n; j++) {
            if (a[j] < a[key]) {    
                key = j;    //    记录数组最小值位置
            }
        }
            if (key != i)
            {
                int tmp = a[key];
                a[key] = a[i];
                a[i] = tmp;    //    交换最小值
            }
   }

5、堆排序
推荐博文:
堆排序详解
图解堆排序过程
详细的递归代码

【基本原理】
先把数组构造成一个大顶堆(父亲节点大于其子节点),然后把堆顶(数组最大值,数组第一个元素)和数组最后一个元素交换,这样就把最大值放到了数组最后边。把数组长度n-1,再进行构造堆,把剩余的第二大值放到堆顶,输出堆顶(放到剩余未排序数组最后面)。依次类推,直至数组排序完成。

堆分为大根堆和小根堆 完全二叉树
大根堆 任意一个父节点的值大于等于其子节点的值【用于升序排列】
小根堆 任意一个父节点的值小于等于其子节点的值【用于降序排列】

【大根堆】
以升序排序为例,利用大根堆的性质(堆顶元素最大)不断输出最大元素,直到堆中没有元素
1.构建大根堆
2.输出堆顶元素与堆底元素交换
3.将堆低元素放一个到堆顶,再重新构造成大根堆,再输出堆顶元素,以此类推
【代码思路】

○  ○  ○  ○  ○  ○  ○
根 左 右
1、若根的索引为0开始,最远的非叶子节点(最后的父节点)按照层次排列:i = n/2 -1 2(n为数目,i为索引)——其左孩纸(2i+1) 右孩子(2i+2)
若根的索引为1开始,则最远的非叶子节点(最后的父节点)按照层次排列:i = n/2
——其左孩纸(2i) 右孩子(2i+1
#include <stdio.h>
#include <stdlib.h>
 
void swap(int* a, int* b)
{
    int temp = *b;
    *b = *a;
    *a = temp;
}
 //从顶到尾迭代实现每一个父节点和子结点的比较和交换
void max_heapify(int arr[], int start, int end) 
{
    //建立父节点指标和子节点指标
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end)  //若子节点指标在范围内才做比较
    {
        if (son + 1 <= end && arr[son] < arr[son + 1]) 
        //先比较两个子节点大小,选择最大的
        son++;
	    if (arr[dad] > arr[son]) //如果父节点大于子节点代表调整完毕,直接跳出函数
	        return;
	    else  //否则交换父子内容再继续子节点和孙节点比较
		    {
		        swap(&arr[dad], &arr[son]);
		        dad = son;
		        son = dad * 2 + 1;
		    }
	}
}
 
void heap_sort(int arr[], int len) 
{
    int i;
    //初始化,i从最后一个父节点开始调整
    //建立一个初始化的大顶堆
    for (i = len / 2 - 1; i >= 0; i--)
    	//迭代实现堆的构建
        max_heapify(arr, i, len - 1);
    //先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完毕
    for (i = len - 1; i > 0; i--) 
    {
        swap(&arr[0], &arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}
 
int main() {
    int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    heap_sort(arr, len);
    int i;
    for (i = 0; i < len; i++)
        printf("%d ", arr[i]);
    printf("\n");
    return 0;
}

6、快速排序
基本原理:
选择一个基准元素,比基准元素小的放基准元素的前面,比基准元素大的放基准元素的后面,这种动作叫分区,每次分区都把一个数列分成了两部分,每次分区都使得一个数字有序,然后将基准元素前面部分和后面部分继续分区,一直分区直到分区的区间中只有一个元素的时候。
快速排序的一趟算法思想是:
1.设置两个变量 i , j ,令 i = 0 ;j = n - 1 ;
2.以第一个数组元素作为关键数据,赋值给key,即 key = A[0];
3.从 j 开始向前搜索,即由后开始向前搜索(j- -),找到第一个小于key的值A[j],将A[j]和A[i]互换;
4.从 i 开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的值A[i],将A[i]和A[j]互换;
5.重复第3、4步,直到 i = j 结束。
领域:
原本在正确位置的元素是不会移动,每一次前后的指针相同时,表示一次排序完成,接着以该位置分为前后两个部分,重复操作。

//快速排序
void QuickSort(int a[], int left, int right)
{
    if (left >= right) {
        return;
    }
    // 定义两个标识
    int i = left;
    int j = right;
    int key = a[left];//将序列的第一个元素设为基数
    int temp;
    while (i < j)
    {
        while (i < j && a[j] >= key)
        {
            j--;
        }
        if (i != j)//如果i j没有相遇,交换位置
        {
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        while (i < j && a[i] <= key)
        {
            i++;
        }
        if (i != j)//如果i j没有相遇,交换位置
        {
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        else
        {
            printArr(a, 8);//打印数组
        }
    }
    QuickSort(a, left, i - 1);
    QuickSort(a, i + 1, right);
}

7、归并排序
基本原理:分治思想
将一个无序的数列一直一分为二,直到分到序列中只有一个数的时候,这个序列肯定是有序的,因为只有一个数,然后将两个只含有一个数字的序列合并为含有两个数字的有序序列,这样一直进行下去,最后就变成了一个大的有序数列

// 合并两个已排好序的数组
void Merge(int a[], int left, int mid, int right)
{
    int len = right - left + 1;        //    数组的长度
    int *temp = new int[len];       // 分配个临时数组
    int k = 0;
    int i = left;                   // 前一数组的起始元素
    int j = mid + 1;                // 后一数组的起始元素
    while (i <= mid && j <= right)
    {
        //    选择较小的存入临时数组
        temp[k++] = a[i] <= a[j] ? a[i++] : a[j++];  
    }
    while (i <= mid)
    {
        temp[k++] = a[i++];
    }
    while (j <= right)
    {
        temp[k++] = a[j++];
    }
    for (int k = 0; k < len; k++)
    {
        a[left++] = temp[k];
    }
}

// 递归实现的归并排序
void MergeSort(int a[], int left, int right)  
{
    if (left == right)    //结束的标志
        return;
    int mid = (left + right) / 2;
    MergeSort(a, left, mid);
    MergeSort(a, mid + 1, right);
    Merge(a, left, mid, right);
}

8、桶排序
桶排序算法详解
1、注意桶范围的选取、排序数值的最小值和最大值的差、或者将原数按照相同的过程进行处理
2、注意相同数的排序

#include <stdio.h>
int main()
{
    int book[1001],i,j,t,n;
    for(i=0;i<=1000;i++)
        book[i]=0;
    scanf("%d",&n);//输入一个数n,表示接下来有n个数
    for(i=1;i<=n;i++)//循环读入n个数,并进行桶排序
    {
        scanf("%d",&t);  //把每一个数读到变量t中
        book[t]++;  //进行计数,对编号为t的桶放一个小旗子
    }
    for(i=1000;i>=0;i--)  //依次判断编号1000~0的桶
        for(j=1;j<=book[i];j++)  //出现了几次就将桶的编号打印几次
             printf("%d ",i);
    getchar();getchar();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章