十大经典排序算法总结(C语言版本)_c语言考研考试哪种排序好用

**平均时间复杂度:**O(n^2);**空间复杂度:**O(1);**稳定性:**不稳定

**适用场景:**选择排序实现也比较简单,并且由于在各种情况下复杂度波动小,因此一般是优于冒泡排序的。在所有的完全交换排序中,选择排序也是比较不错的一种算法。但是,由于固有的O(n^2)复杂度,选择排序在海量数据面前显得力不从心。因此,它适用于简单数据排序。

3、插入排序

插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

3.1 算法描述

**1、**把待排序的数组分成已排序和未排序两部分,初始的时候把第一个元素认为是已排好序的。
**2、**从第二个元素开始,在已排好序的子数组中寻找到该元素合适的位置并插入该位置。
**3、**重复上述过程直到最后一个元素被插入有序子数组中。

3.2 动图演示

3.3 算法实现

//插入排序算法
void InsertSort(int arr[],int numSize)
{
	for(int i=1; i < numSize; i++)
	{
		int value = arr[i];
		int position=i;
		while(position > 0 && arr[position-1] > value)
		{
			arr[position] = arr[position-1];
			position--;
		}
		arr[position] = value;
	}
}

算法结果:

**平均时间复杂度:**O(n^2);**空间复杂度:**O(1);**稳定性:**稳定

**适用场景:**插入排序由于O(n^2)的复杂度,在数组较大的时候不适用。但是,在数据比较少的时候,是一个不错的选择,一般做为快速排序的扩充。

4、归并排序

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

4.1 算法描述

**1、**将序列每相邻两个数字进行归并操作,形成ceil(n/2)个序列,排序后每个序列包含两/一个元素
**2、**若此时序列数不是1个则将上述序列再次归并,形成ceil(n/4)个序列,每个序列包含四/三个元素
**3、**重复步骤2,直到所有元素排序完毕,即序列数为1

4.2 动图演示

特别注意:归并算法的核心是划分归并,下方GIF仅为一种归并排序的措施(与本人写得代码有出入)。

代码的划分与归并流程图;

4.3 算法实现

//归并排序算法
void MergeSort(int arr[],int numSize)
{
	//分配一个辅助数组
	int *tempArr = (int *)malloc(numSize * sizeof(int));
	if(tempArr)
	{
		msort(arr, tempArr, 0, numSize-1);
		free(tempArr);
	}
	else
	{
		printf("ERROR:Failed to allocate memory\r\n");
	}
}

//归并排序算法:1、数组划分
void msort(int arr[],int tempArr[], int left, int right)
{
	//如果只有一个元素,那么就不需要继续划分
	//只有一个元素的区域,本身就是有序的,只需要归并即可
	if(left < right)
	{
		//寻找中间点
		int mid = (left + right)/2;
		//递归划分左半区域
		msort(arr, tempArr, left, mid);
		//递归划分右半区域
		msort(arr, tempArr, mid+1, right);
		//合并已经排序的部分
		merge(arr, tempArr, left, mid, right);
	}
}

//归并排序算法:2、数组归并
void merge(int arr[],int tempArr[],int left,int mid,int right)
{
	//标记左半区第一个未排序的元素
	int l_pos = left;
	//标记右半区第一个未排序的元素
	int r_pos = mid+1;
	//临时数组元素的下标
	int pos = left;
	
	//合并
	while(l_pos <= mid && r_pos <= right)
	{
		if(arr[l_pos] < arr[r_pos])
		{
			tempArr[pos++] = arr[l_pos++];
		}
		else
		{
			tempArr[pos++] = arr[r_pos++];
		}
	}
	
	//合并左半区剩余的元素
	while(l_pos <= mid)
	{
		tempArr[pos++] = arr[l_pos++];
	}
	
	//合并右半区剩余的元素
	while(r_pos <= right)
	{
		tempArr[pos++] = arr[r_pos++];
	}
	
	//把临时数组中合并后的元素复制到原来的数组
	while(left <= right)
	{
		arr[left] = tempArr[left];
		left++;
	}
}

算法结果:

**平均时间复杂度:**O(nlogn);**空间复杂度:**O(n);**稳定性:**稳定

**适用场景:**归并排序在数据量比较大的时候也有较为出色的表现(效率上),但是,其空间复杂度O(n)使得在数据量特别大的时候(例如,1千万数据)几乎不可接受。而且,考虑到有的机器内存本身就比较小,因此,采用归并排序一定要注意。

5、快速排序

快速排序是一个知名度极高的排序算法,其对于大数据的优秀排序性能和相同复杂度算法中相对简单的实现使它注定得到比其他算法更多的宠爱(面试高频的排序算法)。

5.1 算法描述

**1、**从数列中挑出一个元素,称为"基准"(pivot),
**2、**重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
**3、**递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

5.2 动图演示

5.3 算法实现

//快速排序算法
void QuickSort(int arr[],int numSize)
{
	qsort(arr,0,numSize);
}

void qsort(int arr[], int low, int high)
{
  if(low >= high)			//两个索引标号相碰
     return;
  int pivot = partition(arr, low, high);      //将数组分为两部分
  qsort(arr, low, pivot-1);                   //递归排序左子数组
  qsort(arr, pivot+1, high);                  //递归排序右子数值
}

int partition(int arr[], int low, int high)
{
  int pivot = arr[low];     //基准
  while (low < high){
     while (low < high && arr[high] >= pivot) --high;
     arr[low]=arr[high];             //交换比基准大的数值到左端
     while (low < high && arr[low] <= pivot) ++low;
     arr[high] = arr[low];           //交换比基准小的数值到右端
  }
  //扫描完成,基准到位
  arr[low] = pivot;
  //返回的是基准的位置
  return low;
}

算法结果:

**平均时间复杂度:**O(nlogn);**空间复杂度:**O(logn);**稳定性:**不稳定

**适用场景:**快速排序在大多数情况下都是适用的,尤其在数据量大的时候性能优越性更加明显。但是在必要的时候,需要考虑下优化以提高其在最坏情况下的性能。

6、希尔排序

**在希尔排序出现之前,计算机界普遍存在“排序算法不可能突破O(N^2)”的观点。**希尔排序是第一个突破O(n2)的排序算法,它是简单插入排序的改进版。希尔排序的提出,主要基于以下两点:

1、插入排序算法在数组基本有序的情况下,可以近似达到O(n)复杂度,效率极高。
2、但插入排序每次只能将数据移动一位,在数组较大且基本无序的情况下性能会迅速恶化。

**希尔排序核心:**插入算法的升级,通过分组迭代的插入排序缩短排序时间

6.1 算法描述

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
**1、**选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
**2、**按增量序列个数k,对序列进行 k 趟排序;
**3、**每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

6.2 动图演示

6.3 算法实现

//希尔排序
void HillSort(int arr[],int numSize)
{
	//定义增量和步长
	int gap,i,j,k,temp;
	//通过改变组距持续进行插入排序
	for(gap = numSize/2; gap >= 1; gap = gap/2)
	{
		//每个组别进行插入排序
		for(i = 0; i < gap; i++)
		{
			for(j = i + gap; j < numSize; j += gap)		//每个组别中的数据
			{
				//标记需要插入排序的数值
				temp = arr[j];
				for(k = j -gap; arr[k] > temp && k >=0 ; k = k-gap)		//当前gap组别下,前一个数值大于现在这个数值
				{
					arr[k+gap] = arr[k];		//将数值后移一位
				}
				//将插入的数值放入正确位置
				arr[k+gap] = temp;
			}
		}
		
	}
}

算法结果:

**平均时间复杂度:**O(nlogn);**空间复杂度:**O(1);**稳定性:**不稳定

**适用场景:**希尔排序虽然快,但是毕竟是插入排序,其数量级并没有后起之秀——快速排序O(nlogn)快。在大量数据面前,希尔排序不是一个好的算法。但是,中小型规模的数据完全可以使用它。

7、堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。

7.1 算法描述

7.1.1 什么是堆

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子

该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

7.1.2 堆排序基本思想及步骤

**堆排序的基本思想是:**将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

1.假设给定无序序列结构如下

img

2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

img

4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

img

这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

img

此时,我们就将一个无需序列构造成了一个大顶堆。

步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

a.将堆顶元素9和末尾元素4进行交换

img

b.重新调整结构,使其继续满足堆定义

img

c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.

img

后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

img

再简单总结下堆排序的基本思路:

**1、**将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

**2、**将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

**3、**重新调整结构(每次长度递减1),使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

7.2 动图演示

7.3 算法实现

//堆排序
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 HeapSort(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);
    }
}

算法结果:

**平均时间复杂度:**O(nlogn);**空间复杂度:**O(1);**稳定性:**不稳定

**适用场景:**堆排序在建立堆和调整堆的过程中会产生比较大的开销,在元素少的时候并不适用。但是,在元素比较多的情况下,还是不错的一个选择。尤其是在解决诸如“前n大的数”一类问题时,几乎是首选算法。

8、计数排序

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

8.1 算法描述

**1、**找出待排序的数组中最大和最小的元素;
**2、**统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
**3、**对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
**4、**反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

8.2 动图演示

8.3 算法实现

//计数排序
void CountSort(int arr[], int numSize)
{
  if (numSize < 1) return;

  // 寻找最大的元素
  int max = arr[0];
  for (size_t i = 1; i < numSize; i++)
     if (arr[i] > max) max = arr[i];

  // 分配一个长度为max+1的数组存储计数,并初始化为0
  int *count = (int *)malloc(sizeof(int) * (max + 1));
  memset(count, 0, sizeof(int) * (max + 1));

  // 计数
  for (size_t i = 0; i < numSize; i++)
      count[arr[i]]++;

  // 统计计数的累计值
  for (size_t i = 1; i < max + 1; i++)
      count[i] += count[i - 1];

  // 创建一个临时数组保存结果
  int *output = (int *)malloc(sizeof(int) * numSize);

  // 将元素放到正确的位置上
  for (size_t i = 0; i < numSize; i++)
  {
      output[count[arr[i]] - 1] = arr[i];
      count[arr[i]]--;
  }

  // 将结果复制回原数组
  for (size_t i = 0; i < numSize; i++)
      arr[i] = output[i];
}

算法结果:

注意事项: 受限于计数排序自身限制,排序数组的最大值和最小值不要差距过大,否则计算时间很长。

**平均时间复杂度:**O(n+k);**空间复杂度:**O(k);**稳定性:**稳定

**适用场景:**排序目标要能够映射到整数域,其最大值最小值应当容易辨别。例如高中生考试的总分数,显然用0-750就OK啦;又比如一群人的年龄,用个0-150应该就可以了,再不济就用0-200喽。另外,计数排序需要占用大量空间,它比较适用于数据比较集中的情况(正常情况下,仅适用于整数排序)。

9、桶排序

桶排序又叫箱排序,是计数排序的升级版,它的工作原理是将数组分到有限数量的桶子里,然后对每个桶子再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后将各个桶中的数据有序的合并起来。

9.1 算法描述

**1、**找出待排序数组中的最大值max、最小值min
**2、**我们使用 动态数组ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(max-min)/arr.length+1
**3、**遍历数组 arr,计算每个元素 arr[i] 放的桶
**4、**每个桶各自排序
**5、**遍历桶数组,把排序好的元素放进输出数组

9.2 图片演示

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

一直到现在。**

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-z9otreOj-1715680845481)]

[外链图片转存中…(img-jlJsSlZY-1715680845483)]

[外链图片转存中…(img-h2z61d2m-1715680845484)]

[外链图片转存中…(img-LC7hR3r1-1715680845485)]

[外链图片转存中…(img-iNYpDiGC-1715680845486)]

[外链图片转存中…(img-vWHso1RR-1715680845487)]

[外链图片转存中…(img-bgjVClXr-1715680845487)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

  • 29
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值