C语言7大常见排序(详细图解)

目录

今日良言:总有不期而遇的温柔和生生不息的希望

一、排序介绍

二、插入排序

2.1直接插入排序

2.2希尔排序

三、选择排序

3.1选择排序

3.2堆排序

四、交换排序

4.1冒泡排序

4.2快速排序

五、归并排序 


       今日良言:总有不期而遇的温柔和生生不息的希望

一、排序介绍

1.1所谓排序就是对一组数据,按照某个关键字逐渐递增或者逐渐递减排列起来的操作。

如下图:按照价格高低对球鞋进行排序

 1.2常见的7大排序

二、插入排序

核心思路:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

2.1直接插入排序

我们平常打扑克牌就是典型的直接插入排序,如下图

 

 流程图解:

 代码:

#include<stdio.h>
void InsertSort(int* arr, int sz)//按照升序排序
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)//最多是将n-1的数据插入前面
	{
		int end = i;
		int tmp = arr[end + 1];//tmp存放的是end后面第一个数据即需要插入到前面的数据
		while (end >= 0)
		{
			//大于就将end的数据向后移动一位,小于就停止,说明此时end的位置已经为空,直接插入
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];
				end--;//继续让end前面的跟tmp中的数据比较
			}
			else
			{
				break;//说明此时tmp中的元素大于或者等于end的数据
			}
		}
		//直接将tmp即原来end-1位置的数据放入此时end+1的位置即可实现插入
		arr[end+1] = tmp;
	}
}
int main()
{
	int arr[] = { 4,2,3,8,9,5,6,7,1,0 };
	int sz = sizeof(arr) / sizeof(int);
	InsertSort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 2.2希尔排序(缩小增量排序)

2.2.1核心思路:先选定一个整数(gap),把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后取重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

2.2.2  gap>1是预排序,gap=1就是直接插入排序

          gap越大,大的数会越快排到后面,小的数越快到前面

          gap越小,越接近有序

流程图解


 
代码

#include<stdio.h>
void ShellSort(int* arr, int sz)
{
	int gap = sz;
	while (gap > 1)//当间隔大于1的时候进行预排序,等于1就是直接排序,此时已经有序
	{
		gap = gap / 2;//间隔越来越小
		int i = 0;
		for(i=0;i<sz-gap;i++)//把间隔为grap的多组数据同时排
		{
			int end = i;//要排序的位置的前grap个位置的元素
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (arr[end] > tmp)//当end位置的数据大于tmp时,再与end-间隔(grap)的数据进行比较
				{
					arr[end + gap] = arr[end];//将比tmp大的数据依次向后移动间隔grap
					end -= gap;//每次让end减少间隔grap
				}
				else
				{
					break;
				}
			}
			//此时end下标的数据小于tmp。所以将tmp放入到end+grap的下标处
			arr[end + gap] = tmp;
		}
	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(int);
	ShellSort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

三、选择排序

3.1选择排序

核心思路: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 .
为了提高效率,可以在一次遍历找到最大和最小元素的下标,然后分别和数组的起始位置和最后的位置进行交换.

流程图解

 代码:

#include<stdio.h>

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//直接选择排序
void SelectSort(int* arr, int sz)
{
	int begin = 0;
	int end = sz - 1;
	while (begin < end)
	{
		int mini = begin;//最小数的下标mini
		int maxi = begin;//最大数下标maxi
		int i = 0;
		for (i = begin; i <= end; i++)
		{
			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}
			if (arr[i] < arr[mini])
			{
				mini = i;
			}
		}
		//下标为maxi的数据就是最大数,下标为mini的数据就是最小数
		Swap(&arr[begin], &arr[mini]);
		//如果begin和maxi重叠,需要重新修正一下maxi的位置,即交换后的mini的位置
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&arr[maxi],&arr[end]);
		end--;
		begin++;
	}
}
int main()
{
	int arr[] = { 8,7,5,6,1,2,0,3,4,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	SelectSort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

3.2堆排序

3.2.1堆的逻辑结构是一颗完全二叉树,物理结构是一个数组。

3.2.2堆的两个特性:1.结构性:用数组表示的完全二叉树(逻辑结构

                          2.有序性:任一结点的关键字是其子树的最大或者最小值

3.2.3最大堆:父节点的值比孩子结点的值都大

 最小堆:父节点的值比孩子结点的值都小

 3.2.4 升序通过最大堆来实现

     因为,如果选用最小堆实现的话,最小的数是第一个结点,次小的数如论如何选择,剩下的树的结构会乱,不再是最小堆,又需要重新建堆,效率低

    降序通过最小堆来实现

3.2.5建小堆的方法:向下调整算法

    从根节点开始,选出左右孩子较小的那个,跟股结点比较,如果小于父节点就交换值,继续向下调,直到叶子结点结束

这个算法的前提是左右子树都是小堆,才能使用该算法

流程图解:

 如果左右子树不是小堆,那么就从最后一个非叶子结点的子树开始调成小堆

代码(小堆升序):

#include<stdio.h>
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustDown(int* arr, int sz, int root)
{
	int parent = root;
	int child = parent * 2 + 1;//默认是左孩子
	while (child < sz)//child的下标小于sz
	{
		//选出两个孩子比较小的那个
		if ((child + 1 < sz) && (arr[child + 1] < arr[child]))
		{//child+1是为了避免右孩子的下标超过sz,可能没有右孩子
			child += 1;//此时的child的右孩子
		}
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child],&arr[parent]);//如果较小的孩子比双亲节点小,就交换
			parent = child;//将较小的孩子的坐标给双亲节点
			child = parent * 2 + 1;//继续找到左孩子进行下一次的调整
		}
		else
		{
			break;//当没有孩子比双亲小时,结束循环
		}
	}

}
void HeapSort(int* arr, int sz)
{
	int i = 0;
	//将所有子树都调成小堆
	for (i=(sz-1-1)/2;i>=0;i--)//从倒数第一个非叶子结点的子树开始调整成小堆
	{
		AdjustDown(arr,sz, i);
	}
	int end = sz - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);//将最小的数据和第一个数据交换
		AdjustDown(arr, end, 0);
		end--;
	}
}
int main()
{
	int arr[] = { 4,2,0,5,9,7,3,1,6,8 };
	int sz = sizeof(arr) / sizeof(int);
	HeapSort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

大堆升序只需要将这两个'<'换成'>'即可

 

 四、交换排序

交换就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位 置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动

4.1冒泡排序

流程图解:

 代码

 (注意控制边界,小心越界)

void BubbleSort(int* arr, int sz)
{
	int i = 0;
	int j = 0;
	//比较的趟数
	for (i = 0; i < sz - 1; i++)
	{
		//比较的对数
		int flag = 0;//标记是否已经有序
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j + 1] < arr[j])
			{
				Swap(&arr[j + 1], &arr[j]);
				flag = 1;
			}
		}
		if (flag == 0)
		{
			break;
		}
	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(int);
	BubbleSort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

4.2快速排序

核心思路:任取待排序 元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所 有元素都排列在相应位置上为止

流程图解:

快速排序(递归)三种方法:

A.挖坑法

核心思路:先将第一个位置的数据保存用key关键字保存,设置一个开始下标为0的begin左→右找比关键字的,再设置一个开始下标为最后一个数据的下标end,从右→左找比关键字的,同时将开始的位置begin设置为坑(pivot),如果找到了,就将该位置的值放在坑(pivot)位置,然后将此位置当做坑,继续上述操作,直到begin与end相遇,此时将关键字的值放在坑位置即可,这是单趟排序,然后从pivot下标的位置开始分区间  ,[0,pivot-1]  和[pivot+1,right],再进行排序(递归)

流程图解

为了提高快排的效率,可以使用三数取中的方法,避免数据是有序的

注意:只能先从右向左找大,然后再从左向右找小

 代码

#include<stdio.h>

//三数取中
int GetMidIndex(int* arr, int left, int right)
{
	int mid = (left + right) >> 1;
	if (arr[left] < arr[mid])
	{
		if (arr[mid] < arr[right])
		{
			return mid;
		}
		else if (arr[right] < arr[left])
		{
			return left;
		}
		else
			return right;
	}
	else
	{
		if (arr[right] < arr[mid])
		{
			return mid;
		}
		else if (arr[left] < arr[right])
		{
			return left;
		}
		else
			return right;
	}

}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void QuickSort(int* arr, int left, int right)
{
	//如果没有区间(只有一个数据)就结束
	if (left >= right)
		return;
	int index = GetMidIndex(arr, left, right);
	Swap(&arr[left], &arr[index]);
	int begin = left;
	int end = right;
	int key = arr[begin];//关键字
	int pivot = begin;//坑
	while (begin < end)
	{
		//右边找比关键字小的,放在坑位置
		while ((begin < end) && (arr[end] >= key))
		{
			end--;
		}
		arr[pivot] = arr[end];//将比关键字小的数据放入坑
		pivot = end;//将此时end的位置当做坑
		//左边找比关键字大的放在坑位置
		while ((begin < end) && (arr[begin] <= key))
		{
			begin++;
		}
		arr[pivot] = arr[begin];//将比关键字大的放在坑位置
		pivot = begin;//将此时的begin的位置当做坑
	}
	//循环结束后,此时begin和end相等,所指的位置就是坑
	pivot = begin;
	arr[pivot] = key;
	//然后再将关键字左边和右边的分别排序,整体就有序了
	QuickSort(arr, left, pivot - 1);
	QuickSort(arr, pivot + 1, right);
}


int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	QuickSort(arr, 0, sz - 1);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

B.左右指针法

核心思路:key指向第一个位置的下标,设置一个开始下标为0的begin左→右比key所指数据大的,再设置一个开始下标为最后一个数据的下标end,从右→左比key所指数据小的,当begin和end都找到数据以后交换数据,然后继续向后找,直到begin和end相遇,此时将end和begin相遇的下标的值和key中的值交换,这是单趟排序,然后从begin和end相遇的下标的位置开始分区间  ,[0,begin-1]  和[begin+1,right],再进行排序(递归)

 代码

#include<stdio.h>

//三数取中
int GetMidIndex(int* arr, int left, int right)
{
	int mid = (left + right) >> 1;
	if (arr[left] < arr[mid])
	{
		if (arr[mid] < arr[right])
		{
			return mid;
		}
		else if (arr[right] < arr[left])
		{
			return left;
		}
		else
			return right;
	}
	else
	{
		if (arr[right] < arr[mid])
		{
			return mid;
		}
		else if (arr[left] < arr[right])
		{
			return left;
		}
		else
			return right;
	}

}


void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}


int PartSort(int* arr, int left, int right)
{
	int index = GetMidIndex(arr, left, right);
	Swap(&arr[left], &arr[index]);
	int begin = left;
	int end = right;
	int key = begin;
	while (begin < end)
	{
		//右边找小
		while ((begin < end) && (arr[end] >= arr[key]))
		{
			end--;
		}
		//左边找大
		while ((begin < end) && (arr[begin] <= arr[key]))
		{
			begin++;
		}
		//交换begin和end
		Swap(&arr[begin], &arr[end]);
	}
	//交换begin==end时所指数据和key交换
	Swap(&arr[begin], &arr[key]);
	return begin;

}

//快速排序

void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;
	int keyIndex = PartSort(arr, left, right);
	//分治算法  继续对左右区间分别进行排序
	QuickSort(arr, left, keyIndex-1);
	QuickSort(arr, keyIndex + 1, right);
}


int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(int);
	QuickSort(arr, 0, sz - 1);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

C.前后指针法

核心思路:设置第一个数据的下标为prev和key第二个数据的下标为cur,然后让cur开始找比key所指数据小的数据,找到就停下,然后让perv++,为了提高效率,perv++不等于cur,然后再交换perv和cur所指数据,再让cur++,当下标cur>数组最大下标时就结束,此时,将prev所指数据和key所指数据交换,再进行分区间[0,prev-1]和[prev+1,right]  ,分区间再进行上述操作(递归)

流程图解:

 代码

#include<stdio.h>
//三数取中
int GetMidIndex(int* arr, int left, int right)
{
	int mid = (left + right) >> 1;
	if (arr[left] < arr[mid])
	{
		if (arr[mid] < arr[right])
		{
			return mid;
		}
		else if (arr[right] < arr[left])
		{
			return left;
		}
		else
			return right;
	}
	else
	{
		if (arr[right] < arr[mid])
		{
			return mid;
		}
		else if (arr[left] < arr[right])
		{
			return left;
		}
		else
			return right;
	}

}


void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}


void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
		return;
	int mid = GetMidIndex(arr, left, right);
	Swap(&arr[left], &arr[mid]);//避免队列有序
	int cur = left+1;
	int prev = left;
	int key = left;
	while (cur <= right)
	{
		//找比key小的
		if ((arr[cur] < arr[key]) && (prev++ != cur))//后面避免自己与自己交换
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;//cur往后继续找
	}
	//交换perv与刚开始key的值
	Swap(&arr[prev], &arr[key]);
	QuickSort(arr, left, prev - 1);
	QuickSort(arr, prev + 1, right);

}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(int);
	QuickSort(arr, 0, sz - 1);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

五、归并排序(递归)

核心思路:是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列。即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

流程图解

首先需要开辟与原数组大小相同的动态内存空间来存放排序时的数据,从最小的子序列开始排序,将两个子序列依次将较小的值放入开辟的动态数组中,最后再将排好的数据拷贝回原数组

 代码:

#include<stdio.h>
#include<stdlib.h>

void _MergeSort(int* arr, int left, int right, int* tmp)
{
	if (left >= right)
		return;
	int mid = (left + right) >> 1;
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int index = left;
	//归并
	while ((begin1 <= end1) && (begin2 <= end2))
	{
		if (arr[begin1] < arr[begin2])
		{
			tmp[index++] = arr[begin1++];
		} 
		{
			tmp[index++] = arr[begin2++];
		}
	}
	//如果有一个区间结束,另一个还有数据要继续加到后面
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	//拷贝回去
	int i = 0;
	for (i = left; i <= right; i++)
	{
		arr[i] = tmp[i];
	}
}


//归并排序
void MergeSort(int* arr, int sz)
{
	int* tmp = (int*)malloc(sizeof(int) * sz);
	if (tmp == NULL)
	{
		perror("");
		exit(-1);
	}
	else
	{
		_MergeSort(arr, 0, sz - 1, tmp);
		free(tmp);
		tmp == NULL;
	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(int);
	MergeSort(arr, sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

  • 51
    点赞
  • 312
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值