排序算法详细总结(C语言)

排序算法总结

基本概念

  • 定义:排序是将一批无序的记录(数据)重新排列成按关键字有序的记录序列的过程。
  • 分类:
    • 内部排序:待排序的记录数不太多,所有的记录都能存放在内存中进行排序。
    • 外部排序:待排序的记录数太多,所有的记录不可能存放在内存中, 排序过程中必须在内、外存之间进行数据交换。
排序
交换排序类
冒泡排序
快速排序
选择排序类
简单选择排序
堆排序
插入排序类
直接插入排序
希尔排序
归并排序类
归并排序
  • 稳定性:
    • 稳定排序:对于关键字相等的记录,排序前后相对位置不变。
    • 不稳定排序:对于关键字相等的记录,排序前后相对位置可能发生变化。

交换排序类

冒泡排序

参考文章:http://c.biancheng.net/view/3444.html

  • 原理:两两相邻的关键字,如果反序则交换
  • 图解:对下面的无序表进行升序排序
    第一轮冒泡排序
    第二轮冒泡排序
  • C语言代码:
void BubbleSort(int a[], int n)
{
	int i, j, flag=1, temp;
	for(i = n-1; i > 0 && flag == 1; i--)
	{
		flag = 0;
		for(j = 1; j <= i; j++)
			if(a[j] > a[j+1])
			{
				flag = 1;
				temp = a[j+1];  // 交换
				a[j+1] = a[j];
				a[j] = temp;
			}
	}
}
void BubbleSort(int arr[], int n)
{
	int i, j, flag, temp;
	for(i = 0; i < n-1; i++)	 // 外层循环控制冒泡排序的轮数,n-1轮
	{
		flag = 0;
		for(j = 0;j < n-1-i; j++)	// 内层循环控制每轮冒泡排序比较的次数
			if(arr[j] > arr[j+1])	
			{
				flag = 1;
				temp = arr[j+1];
				arr[j+1] = arr[j];
				arr[j] = temp;
			}
		if(flag == 0)	break;	// 表中记录排序完成
	}
}

快速排序

参考文章:https://www.cnblogs.com/skywang12345/p/3596746.html
挖坑填数+分治法

  • 原理:
    1.先从数列中取出一个数作为基准数。
    2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
    3.再对左右区间重复第二步,直到各区间只有一个数。
  • 图解:

    这个是原理的1和2
  • C语言代码:
/*
 * 快速排序
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     l -- 数组的左边界(例如,从起始位置开始排序,则l=0)
 *     r -- 数组的右边界(例如,排序截至到数组末尾,则r=a.length-1)
 */
void quick_sort(int a[], int l, int r)
{
    if (l < r)
    {
        int i,j,x;
        i = l;
        j = r;
        x = a[i];
        while (i < j)
        {
            while(i < j && a[j] > x)
                j--; // 从右向左找第一个小于x的数
            if(i < j)
                a[i++] = a[j];
            while(i < j && a[i] < x)
                i++; // 从左向右找第一个大于x的数
            if(i < j)
                a[j--] = a[i];
        }
        a[i] = x;
        quick_sort(a, l, i-1); /* 递归调用 */
        quick_sort(a, i+1, r); /* 递归调用 */
    }
}

选择排序类

简单选择排序

参考文章:http://c.biancheng.net/view/3446.html

  • 原理:对于具有 n 个记录的无序表遍历 n-1 次,第 i 次从无序表中第 i 个记录开始,找出后序关键字中最小的记录,然后放置在第 i 的位置上。
  • 图解:例如对无序表{56,12,80,91,20}采用简单选择排序算法进行排序,具体过程为:
    1. 从下标为1的位置开始,遍历后面关键字,如果有比下标为0小的,同下标为0的关键字交换。
    2. 从下标为2的位置开始,遍历后面关键字,如果有比下标为1小的,同下标为1的关键字交换。
    3. 从下标为3的位置开始,遍历后面关键字,如果有比下标为2小的,同下标为2的关键字交换。
    4. 从下标为4的位置开始,遍历后面关键字,如果有比下标为3小的,同下标为3的关键字交换。
  • C语言代码:
void SelectionSort(int arr[], int n)		//升序
{
	int i, j, temp, min;
	for(i = 0; i < n; i++)
	{
		min = i;
		
		for(j = i+1; j < n; j++)
			if(arr[min] > arr[j])	min = j;
			
		if(min != i)	//如果 j 和 i 不相等,说明最小值不在下标为 i 的位置,需要交换
		{
			temp = arr[min];
			arr[min] = arr[i];
			arr[i] = temp;
		}
	}
}

堆排序

参考文章:https://www.cnblogs.com/linhaostudy/p/11761994.html

  • 基本概念:
    堆排序是一种树形选择排序,在排序过程中可以把元素看成是一颗完全二叉树,每个节点都大(小)于它的两个子节点,当每个节点都大于等于它的两个子节点时,就称为大顶堆,也叫堆有序; 当每个节点都小于等于它的两个子节点时,就称为小顶堆。
    即:

    • k(i) ≤ k(2i) 且 k(i) ≤ k(2i+1)(在 n 个记录的范围内,第 i 个关键字的值小于第 2i 个关键字,同时也小于第 2i+1 个关键字)
    • k(i) ≥ k(2i) 且 k(i) ≥ k(2i+1)(在 n 个记录的范围内,第 i 个关键字的值大于第 2i 个关键字,同时也大于第 2i+1 个关键字)
  • 原理:
    1.将长度为n的待排序的数组进行堆有序化构造成一个大顶堆
    2.将根节点与尾节点交换并输出此时的尾节点
    3.将剩余的n -1个节点重新进行堆有序化
    4.重复步骤2,步骤3直至构造成一个有序序列

  • 图解:
    eg:{5, 2, 6, 0, 3, 9, 1, 7, 4, 8}

    1.最大堆的构建








    2.将最大值和最后的一个元素交换

    3.将剩余的结点再次进行堆构造,回到第一步

    4.将最大值和最后的一个元素交换

    5.回到第一步,最终结果:

  • 个人理解
    构造最大堆的过程:
    在这里插入图片描述
    父结点下沉、孩子结点上浮:指的是父结点与孩子结点中最大的比较,孩子结点中最大的上浮,如下所示:
    在这里插入图片描述
    在这里插入图片描述
    接下来以根结点为2所在的子树进行分析:
    在这里插入图片描述
    最后就是到了这棵树的根结点下沉:
    在这里插入图片描述
    排序的过程:
    上面大顶堆已经构造完成,我们现在需要排序,每次将最大的元素和最后一个元素进行交换,然后将剩余元素构成的二叉树重新调整,即对根结点进行下沉、孩子结点进行上浮操作。
    在这里插入图片描述

  • 算法分析
    时间效率:O(nlog2n) 对初始序列不敏感,最好、最坏和平均时间复杂度均为O(nlog2n)
    空间效率:O(1):只需要一个辅助存储空间
    稳 定 性:不稳定,适用于n 较大的情况,初始建堆所需的比较次数较多,不适合序列长度较小的情况。

  • C语言代码:

// 大顶堆的构造,对父结点进行下沉、孩子结点上浮
// 注意树的根结点为k[1],则左、右孩子结点分别为k[2]和k[3]
void HeapAdjust(int k[], int p, int n)
{
    int i,temp;
    temp = k[p];  // 备份父节点的值
    for (i = 2 * p; i <= n; i*=2)    // 逐渐去找左右孩子结点
    {
        // 找到两个孩子结点中最大的
        if (i < n && k[i] < k[i + 1])
            i++;
        // 父节点和孩子最大的进行判断
        if (temp >= k[i])  // 如果父结点大于孩子结点,则已经为最大堆,终止父结点下沉、孩子结点上浮操作
            break;
        k[p] = k[i];  // 孩子结点上浮
        p = i;        // 更新该子树的根结点作为父节点
    }
    // 当我们在for循环中孩子结点上浮,最后一次上浮的孩子结点的值更新为最开始时父节点的值
    // 若没有找到,相当于其值不变
    k[p] = temp;
}

void swap(int k[], int i, int j)
{
    int temp = k[i];
    k[i] = k[j];
    k[j] = temp;
}

// 大顶堆排序,注意树的根结点为k[1]
void HeapSort(int k[], int n)
{
    int i;
	
    // 首先将无序数列转换为大顶堆
    for (i = n / 2; i > 0;i--)    // 由于是完全二叉树,所以我们从一半向前构造,传入父节点
        HeapAdjust(k, i, n);

    // 上面大顶堆已经构造完成,我们现在需要排序,每次将最大的元素和最后一个元素进行交换
    // 然后将剩余元素重新调整,即对根结点进行下沉、孩子结点进行上浮操作
    for (i = n; i > 1; i--)
    {
        swap(k, 1, i);
        HeapAdjust(k, 1, i - 1);
    }
}

插入排序类

直接插入排序

参考文章:http://c.biancheng.net/view/3439.html

  • 原理:在添加新的记录时,使用顺序查找的方式找到其要插入的位置,然后将新记录插入。
  • 图解:例如采用直接插入排序算法将无序表{3,1,7,5,2,4,9,6}进行升序排序的过程为:





  • C语言代码:
void InsertSort(int arr[], int n)
{
	int i, j, temp;
    for(i = 1; i < n; i++)
        if(arr[i] < arr[i-1])	//若第 i 个元素大于 i-1 元素则直接插入;反之,需要找到适当的插入位置后在插入。
        {
            temp = arr[i];
			//采用顺序查找方式找到插入的位置,在查找的同时,将数组中的元素进行后移操作,给插入元素腾出空间
			for(j = i-1; j > -1 && temp < arr[j]; j--)
                arr[j+1] = arr[j];
            arr[j+1] = temp;      //插入到正确位置
        }
}
void InsertSort(int a[], int n)
{
	int i, j;
	for(i = 2; i <= n; i++)
		if(a[i] < a[i-1])        // 将a[i]插入有序子表
		{
			a[0] = a[i];         // 复制为哨兵 
			for(j = i-1; a[0] < a[j]; j--)
				a[j+1] = a[j];   // 记录后移 
			a[j+1] = a[0];       // 插入到正确位置
		}
}

折半排序

void BInsertSort(int a[], int size)
{
    int i, j, low, high, mid;
    int temp = 0;
    for (i=1; i<size; i++)
	{
        low = 0;
        high = i-1;
        temp = a[i];
        
        while (low <= high) // 采用折半查找法判断插入位置,最终变量 low 表示插入位置
		{
            mid = (low + high) / 2;
            if(temp < a[mid])	high = mid-1;
			else	low = mid + 1;
        }
        for (j = i-1; j >= low; j--)  // 有序表中插入位置后的元素统一后移
            a[j+1] = a[j];
        a[low] = temp; //插入元素
    }
}
void BInsertSort(int a[], int n)
{
	int i, j, low, high, mid;
	for(i=2; i <= n; i++)
	{
		a[0] = a[i]; // 复制为哨兵
		
		low = 1; high = i-1;
		while (low <= high)
		{
			mid = (low + high) / 2;        // 折半
			if (a[0] < a[mid]) high = mid-1; // 插入点在低半区
			else low = mid + 1 ;           // 插入点在高半区
		}
		
		for(j = i-1; j >= low; j--) 
			a[j+1] = a[j];               // 记录后移
		a[low] = a[0];                   // 插入到正确位置
	}
}

希尔排序

参考文章:https://blog.csdn.net/qq_39207948/article/details/80006224

  • 原理:先取一个小于n的整数d,作为第一个增量,把所有距离为d的数据分组,再对每一组进行直接插入排序,最后合并。接着,增量减半,分组排序合并,重复下去直到增量为1.
  • 图解:






    最后在进行一次直接插入排序就OK了。
  • C语言代码:
void ShellSort(int arr[], int n)
{
	int i, j, temp, gap;	// gap是分组的步长
	for(gap = n/2; gap > 0; gap = gap/2)
	{
		for(i = gap; i < n; i++) // 每组交替插入排序
		{	
			if(arr[i] < arr[i - gap])
			{
				temp = arr[i];
				for(j = i - gap; j > -1 && temp < arr[j]; j = j - gap)
					arr[j + gap] = arr[j];  // 记录后移
				arr[j + gap] = temp;        // 插入到正确位置
			}
		}
	}
}
void ShellInsert(int a[], int dk, int length)
{
	int i, j;
	for(i = dk+1; i <= length; i++)  // 每组交替插入排序
		if(a[i] < a[i-dk])
		{         
			a[0] = a[i];          // 复制为哨兵 
			for(j = i-dk; j > 0 && (a[0] < a[j]); j = j-dk)
				a[j+dk] = a[j];   // 记录后移
			a[j+dk] = a[0];       // 插入到正确位置
		}
}

void ShellSort(int a[], int n)
{
	int dk;
	for(dk = n/2; dk > 0; dk /= 2)  // 增量从n/2开始,每次除2
		ShellInsert(a, dk, n);
}

归并排序类

归并排序

参考文章:https://zhuanlan.zhihu.com/p/124356219

  • 原理:
    分解(Divide):将n个元素分成个含n/2个元素的子序列。
    解决(Conquer):用合并排序法对两个子序列递归的排序。
    合并(Combine):合并两个已排序的子序列已得到排序结果。
  • 图解:

  • C语言代码:
    迭代法:
    ① 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
    ② 设定两个指针,最初位置分别为两个已经排序序列的起始位置
    ③ 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
    ④ 重复步骤③直到某一指针到达序列尾
    ⑤ 将另一序列剩下的所有元素直接复制到合并序列尾
int min(int x, int y) {
    return x < y ? x : y;
}
void merge_sort(int arr[], int len) {
    int* a = arr;
    int* b = (int*) malloc(len * sizeof(int));
    int seg, start;
    for (seg = 1; seg < len; seg += seg) {
        for (start = 0; start < len; start += seg + seg) {
            int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len);
            int k = low;
            int start1 = low, end1 = mid;
            int start2 = mid, end2 = high;
            while (start1 < end1 && start2 < end2)
                b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
            while (start1 < end1)
                b[k++] = a[start1++];
            while (start2 < end2)
                b[k++] = a[start2++];
        }
        int* temp = a;
        a = b;
        b = temp;
    }
    if (a != arr) {
        int i;
        for (i = 0; i < len; i++)
            b[i] = a[i];
        b = a;
    }
    free(b);
}

递归法:
① 将序列每相邻两个数字进行归并操作,形成floor(n/2)个序列,排序后每个序列包含两个元素
② 将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
③ 重复步骤②,直到所有元素排序完毕

void merge_sort_recursive(int arr[], int reg[], int start, int end)
{
    if (start >= end) return;
	
    int len = end - start, mid = (len >> 1) + start;
    int start1 = start, end1 = mid;
    int start2 = mid + 1, end2 = end;
    merge_sort_recursive(arr, reg, start1, end1);
    merge_sort_recursive(arr, reg, start2, end2);
	
    int k = start;
    while (start1 <= end1 && start2 <= end2)
        reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
    while (start1 <= end1)
        reg[k++] = arr[start1++];
    while (start2 <= end2)
        reg[k++] = arr[start2++];
	
    for (k = start; k <= end; k++)
        arr[k] = reg[k];
}

void MergeSort(int arr[], const int len) {
    int *tmp = (int *)malloc(len*sizeof(int));
    merge_sort_recursive(arr, tmp, 0, len - 1);
    free(tmp);
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值