【C语言】经典_排序_算法

        排序在生活中很多,比如网上买东西时候要根据条件进行排序,在编程学习中也是必须学习的,排序的方法有很多,但是效率各有各的差别。


一、插入排序

// 插入排序
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n;i++) {
		int end = i;
		int temp = a[end+1];

		while (end >= 0)
		{
			if (temp < a[end]) {
				a[end+1] = a[end];
				end--;
			}
			else {
				break;
			}
			a[end+1] = temp;
		}
	}
}

思路:

end比end+1大,把 end 对应的数据往后覆盖,end位置往后移动。

然后继续往前比较

 

到头了把end+1插入的值插入到头部去。

 

这样一次排序就完成了,多次插入进去就会发现,数组变为了升序的数组。

        

PS:插入排序的时间复杂度为O(N^2)。但是如果数组有序或者比较接近有序则效率会快很多。

二、希尔排序

  希尔排序(Shell's Sort)是插入排序的一种,又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。

思路:

        其主要思路是,如果插入排序在接近有序的情况下,效率大大提高,那么可以不可以人为的制造数组有序的情况。

        这里定义了一个 gap ,这里gap定义为 3,把数组分为了三组,每个相距gap个距离为一组,对这三个组进行预排序。

  

    int gap = 3;
	
	for (int j = 0; j < gap; j++) {

		for (int i = j; i < n - gap; i += gap) {

			int end = i;
			int temp = a[end+gap];

			while (end >= 0)
			{
				if (temp < a[end]) {
					a[end+gap] = a[end];
					end -= gap;
				}
				else {
					break;
				}
				a[end + gap] = temp;
			}
		}
	}

        会发现每个组的排序都是插入排序,这里代码还可以优化,这里是一个组一个组的排序,导致有三层循环,其实只用使用两层循环。

    int gap = 3;

	for (int i = 0; i < n - gap; i++) {

		int end = i;
		int temp = a[end+gap];

		while (end >= 0)
		{
			if (temp < a[end]) {
				a[end+gap] = a[end];
				end -= gap;
			}
			else {
				break;
			}
			a[end + gap] = temp;
		}
	}	

        这里其实就改了一个 i ++,这里的思路是,不用分组进行预排序,而是排完一组就进入下一组。结果也是一样的,而且非常巧妙。

        到这里就完成了一次预排序,这里还要进行多次预排序才行,那么gap怎么选择呢?一般这里选择其最开始等于数组的大小,然后慢慢缩小,直到等于1。

  • 排升序,gap越大,排大的数更快的到后面,小的数可以更快到前面,但是越不接近有序。
  • 排升序,gap越小,越接近有序的。当gap==1,就是插入排序。
  • gap=n(数组长度)
  • gap=gap/3+1;(要预排序很多次)
  • gap>1时是与排序,gap最后一个等于1是直接插入排序。

代码:

void ShellSort(int* a, int n)
{
	int gap = n;

	while(gap > 1){

        gap = gap/3+1;

        for (int i = 0; i < n - gap; i++){

            int end = i;
            int temp = a[end+gap];

            while (end >= 0)
            {
                if (temp < a[end]) {
                    a[end+gap] = a[end];
                    end -= gap;
                }
                else {
                    break;
                }

                a[end + gap] = temp;
            }
        }	
	}
}

测试:

 时间复杂度:

        O(N^1.3)

计算gap时+1可以忽略, gap/3/3/3/3/3/...=N,那么预排为 log3N。

for循环里面是2/3*N。

主要是while循环里面该如何计算,其数学计算比较复杂,主要讲解其思路。

1.gap很大的时候,其大概是2N次。

        gap很大的时候,跳的很快。

2.gap很小的时候,其大概也是2N次。

        gap很小的时候,前面已经预排序过,所以数组接近有序了,所以循环也走的很快。

 就是N*log3N(注意这里是推算,不准确,记个O(N^1.3)就行)。

三、堆排序

// 堆排序  建小堆
void AdjustDwon(int* a, int n, int parent)
{
	int child = parent * 2 + 1;

	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child]) { 
            //注意child+1<size要在前面判断,不然越界
			child++;
		}

		if (a[child] < a[parent]) {
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			break;
		}
	}
}

//小堆建立的降序
void HeapSort(int* a, int n) 
{
	for (int i = (n - 1 - 1) / 2; i >= 0;i--) {  //注意这里是i>=0
		AdjustDwon(a, n, i);
	}

	int size = n - 1;
	while (size>0) {
		swap(&a[0],&a[size]);
		AdjustDwon(a,size,0);//注意这里使用的size
		size--;
	}
}

        前面有,不赘述。 

堆的介绍:https://blog.csdn.net/weixin_45423515/article/details/124916001?spm=1001.2014.3001.5501

四、冒泡排序

// 冒泡排序
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n-1;i++) {
		for (int j = 0; j < n-i-1;j++) {

			if (a[j+1]<a[j]) {
				swap(&a[j+1],&a[j]);
			}

		}
	}
}

        非常容易理解的排序,也是最开始学的排序。其思路也非常简单,后面一个前面一个数大就交换。

时间复杂度:

        O(N^2) 

五、选择排序

// 选择排序
void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	while (begin<end) {

		int max = begin, min = begin;
		for (int i = begin ; i <= end;i++) { //这里只有4个数的时候begin+1是错误的

			if (a[i] < a[min])
				min = i;
			if (a[i] > a[max])
				max = i;
		}
		swap(&a[begin], &a[min]);

		if (begin ==  max) {
			max = min;
		}

		swap(&a[end],&a[max]);

		begin++;
		end--;
	}
}

        其思路也比较简单,遇到小的数就放前面,大的数放后面,非常消耗效率,而且不像插入排序,如果数组接近有序就会提上效率,不管数组有没有序其时间复杂度都是最坏的O(N^2)。

PS:这里要注意,如果begin是最大的数,这里交换后最大的数会换位子,所以这里要判断下。

时间复杂度:

        O(N^2) 

六、快速排序(递归法)

        

// 快速排序递归实现
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end) {
		return;
	}

	int  left = begin , right = end ;
	int key = left;

	while (left < right ) {
        //注意找的是纯小于key的数,所以要用<=,>=
        //为了防止一直找不到,要加上left<right防止越界

        while (left < right && a[right] => a[key]) { //右边找小
			right--;
		}

		while (left < right && a[left] <= a[key]) { //左边找大
			left++;
		}

		

		swap(&a[left] , &a[right]); //找到后交换
	}

	swap(&a[key],&a[left]);

	QuickSort(a,begin,left - 1);
 
	QuickSort(a,left + 1,end);
}

思路:

        让最左边的数作为一个key(最右边的也可以),然后让数组其余的数比较key,如果比key大放右边,比key小放左边。

        两边相互找,找到后交换。找到最后(left和right相遇时)与key进行交换,这样key就到了最合适的位置上。

        然后以分治的思想,让两边分别再排序。

要注意开始从哪边开始查找:

        这里要注意如果key选的左边,那么就要让右边开始走(选的右边,左边开始走),其原因是要保证相遇的位置的值,要比key值小。从右边走停下来的值固定是比key小的值,就可以跟key进行交换。

        如果从左边开始走,假设遇到的都是小于key的值,到最后遇到right时的位置刚好比key大,再交换,会发现把大的值换到开头去了,这是错误的。

 

 

        如果从右边开始走,假设遇到的都是比key大的值,right会找小于key的数一直找到跟left相遇,left此时还是key,自己跟自己交换,也说明了其他数都比key大,开头的位置就是最合适的位置。(为什么要从右边开始走的列子有很多,可以只记这一个较简单的列子)

测试:

猜你喜欢

转载自blog.csdn.net/weixin_45423515/article/details/125133131
今日推荐