排序

排序

(重点:快排,堆排,希尔,归并排序)以及它们的时间复杂度

插入排序

  • **直接插入排序(**洗牌,对已经有序的元素,进来新的时候进行排序,适合接近有序,数量较小)-------O(N方)
    5 4 3 2 1
    5| 4 3 2 1(第一个数看做有序)
    4 5| 3 2 1,插入一个数后任然有序
    3 4 5| 2 1
    2 3 4 5| 1
    1 2 3 4 5
    时间复杂度:最多搬移n-1次 ,最多比较n-1次,
    稳定性:稳定,相邻位置排完序,相对顺序不变,变化不是很明显,就是稳定
    核心:要插入的时候,前面已经是排好序的了,先把这个要 插入的数给“拿走”----保存起来。然后和它的前一个数值比较,搬移,在和前前一个比较…

代码实现:

void InsertSort(int *arr, int n)///
{
	int i = 0;
	for (; i<n - 1; i++)     //n-1的原因
	{
		int end =i ;
		int tmp = arr[end + 1];
		while (end >= 0 && arr[end] > tmp)   //end越界问题
		{
			arr[end + 1] = arr[end];
			--end;
		}
		arr[end + 1] = tmp;
	}
	

}

  • 希尔排序---------数据量大,数据凌乱,不稳定

  • 思想n个数字,用力个增量进行分组,每个组单独进行排序,然后缩小增量,直至增量为1,排序,(百度一下)
    第一次分组排序后,大的尽量往后走,小的尽量往前走(gap = 3)-------接近有序
    第二次减小gap在分组,以上面的同样方法(gap = 2)--------越来越接近有序
    … (gap = 1)-----------有序
    gap的取值最好是gap/3+1
    代码实现:

void ShellSort(int * arr,int n)
{
	int gap = n;
	while (gap > 1)
	{

		 gap = gap / 3 + 1;   //为什么是/3+1,+1保证 它 最后一次为1,取多少合适; 

		int i = 0;
		for (; i<n - gap; ++i)   //按道理i应该 是i+gap,但是不需要,因为i++之后进入了第二组
		{
			//为什么是n-gap?直接插入排序gap = 1;

			int end = i;
			int tmp = arr[end + gap];
			while (end>0 && arr[end]>tmp)
			{
				arr[end + gap] = arr[end];
				end -= gap;
			}
			arr[end + gap] = tmp;

		}
	}
	
}

测试希尔排序和直接插入排序。来检测希尔排序的效率—随机给10000个数字,分别用希尔和直接插入排序来排序,观察所用的时间

void test2()
{
	const int n = 100000;
	int * a1 = (int *)malloc(sizeof(int)*n);
	int * a2 = (int *)malloc(sizeof(int)*n);
	srand(time(0));
	for (int i = 0; i < n; i++)
	{
		a1[i] = rand();
		a2[i] = a1[i];
	}
		size_t  begin1 = clock();
		InsertSort(a1, n);
		size_t  end1 = clock();

		size_t  begin2 = clock();
		ShellSort(a2,n);
		size_t  end2 = clock();
		

	printf("%u \n", end1 - begin1);
	printf("%u\n", end2 - begin2);


}

看的出希尔排序的效率还是很高的
在这里插入图片描述

选择排序

每一趟(第i趟,i=0,1,…,n-2)在后面n-i个待排序的数据元素集合中选
出关键码最小的数据元素,作为有序元素序列的第i个元素。待到第n-2趟
做完,待排序元素集合中只剩下1个元素,排序结束。

  1. 直接选择排序--------没趟做了很多重复的比较,退排序解决了这个问题
    思想:在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
    若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中
    的最后一个(第一个)元素交换,在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,
    重复上述步骤,直到集合剩余1个元素
    每次 选出最小的元素,然后和第一个位置交换,缩小范围,在从剩下的数中选出次小的,和二个位置,用下标来操作
    在这里插入图片描述
void SelectSort(int * arr, int size)   //直接选择选择排序
{
	for (int i = 0; i < size-1; i++)    //剩下最后一个不用选    
	{
		int MinPos = i;
		for (int j = i; j < size; j++)     
		{
			if (arr[MinPos]>arr[j])
			{
				MinPos = j;
			}
		}
		Swap(&arr[MinPos],&arr[i] );
	}
	
}

优化实现:每次从数据中选出最大的一个数和最小的一个数,最大的和最后位置交换,最小的和第一个位置换,缩小区间,重复工作
在这里插入图片描述

void  SelectSort2(int * arr,int size)   //优化----选择排序
{
	int begin = 0;
	int end = size - 1;
	while (begin<end)                   
	{                                 
		int min = begin;
		int max = begin;
		for (int i =begin; i <=end; i++)  //在begin和end区间取选最大值,最小值
		{
			if (arr[min]> arr[i])
			{
				min = i;   
			}
 			if (arr[max] < arr[i])
			{
				max = i;    
			}
		}
		if (min != begin)  //交换
		{
			Swap(&arr[min], &arr[begin]);       //1 6 8 2 5 0
		}           //有个坑,当先进行大的交换的时候,min正好处于end位置,此时要进行更新min的位置,把它更新到max的位置 
		          //或者max在 begin的 位置
		if (begin == max)     //相反如果先交换大的,就要考虑end上的位置是不是为min,就要更新min
		{
			max = min;
		}
		if (max != end)
		{
			Swap(&arr[max], &arr[end]);
		}
		
		begin++;
		end--;
	}
	
}
  1. 堆排序-----升序(大堆),降序(小堆),不稳定,时间复杂度是多少

交换排序

  1. 冒泡排序----------时间复杂度,是稳定的
    思想:每一趟遍历把最大的(最小)的数据不断的交换到最后,就像冒泡一样浮了上来,然后在来一趟,需要n-1趟

  2. 快排
    思想:取一个基准值,(升序),比基准值大的放它的右边,小的放它的左侧
    在这里插入图片描述
    hoare版本
    在这里插入图片描述

挖坑法
在这里插入图片描述
前后指针法
递归排序的优化:1.三数取中选Key法。2.一开始可以考虑递归,一直递归到小的区间时可以考虑用直接插入排序
快排的非递归
快排的时间复杂度

//快排(重要)
//思想:单趟选出关键字(第一个,或者最后一个),放在合适的位置后,比他小的放左边,比它大的放右边
//如何放在合适的位置,三种方法(左右指针法,挖坑法,前后指针法)
//三数取中(为什么用它)
int GeTMiddle(int*arr, int left, int right)
{
	int mid = left + (right - left) >>1;
	if (arr[left] < arr[right])
	{
		if (arr[mid] < arr[left])
			Swap(&arr[left], &arr[mid]);
		else if (arr[mid]>arr[right])
			Swap(&arr[right], &arr[mid]);
		else
			return mid;
	}
	return mid;
}
//左右指针法
int  part1(int* arr,int left,int right)  //单趟排序
{
	int key = arr[right];
	int begin = left;
	int end = right;
	while (begin < end)
	{
		//begin找大
		while (begin < end && arr[begin] <= key)  // =坑,没有=就停下来了,越界的坑

			++begin;

		//end找小的
		while (begin < end && arr[end] >= key)  //考虑一下不加=

			--end;

		
		if (begin < end)
			Swap(&arr[begin], &arr[end]);       //1 2 3 4 
	}
	//begin 和end相遇的情况(begin先停下,end找begin,两者相遇的数比key大,把它放在后边,反过来两者相遇的数比key小,放在前边

		//begin end相遇,停止
		Swap(&arr[begin], &arr[right]);
	
	return arr[begin];
}


//方法二,挖坑法
int Part2(int* arr,int left,int right)
{
	int key = arr[right];
	int begin = left;
	int end = right;
	while (begin < end)
	{
		while (begin < end && arr[begin] < key)
		{
			++begin;
		}
		arr[end] = arr[begin];  //补坑
		while (begin < end && arr[end] >= key)
		{
			--end;
		}
		arr[begin] = arr[end];
	
	}
	//相遇的位置
	arr[begin] = key;
	return begin;
    
}

归并排序------等半划分

基本思想: 将待排序的元素序列分成两个长度相等的子序列,对每一个子序列排序然后将他们合并成一个序列。合并两个子序列的过程称为二路归并
在这里插入图片描述
重点是划分到最后一组的时候(即每组十五年感谢一个的时候怎么进行归并,其实就是两个数组借助第三个数组进行排序,比较,小的放进去,在比较,在放小的,所以归并是归并到一个新的数组中然后拷到原数组中,至于划分的时候其实是借助下标来控制区间的
第一:对区间划分成(递归划分)
第二:合并(两个有序数组合并成了一个数组)

void MergertData(int*arr, int left, int mid, int right, int *tmp)
{                         //要划分成左右两个区间,所以得传左右区间数,中间数,
	int beginL = left, endL = mid;     //先划分两段 ,这是左数组
	int beginR = mid+1, endR = right;  //右数组
	int index = left;
	while (beginL < endL && beginR < endR)
	{
		if (arr[beginL] <= arr[beginR])    //左数组中的第一个数字和右边数组的第一个数字
			tmp[index++] = arr[beginL];
		else
			tmp[index++] = arr[beginR];
	}

	//左边界里的没搬完
	while (beginL < endL)

	{
		tmp[index++] = arr[beginL++];
	}

	
    while(beginR < endR)
	{
		tmp[index++] = array[beginR++];
	}
}
void _MergertSort(int* arr, int left, int right, int* tmp)    //划分方式
{
	if (right - left > 1)
	{
		int mid = (left + (right - left) >> 2);
		_MergertSort(arr, left, mid, tmp); //左边划分
		_MergertSort(arr, mid + 1, right, tmp);  //右边划分
		MergertData(arr, left, mid, right, tmp);//   归并起来
	}
}

void MergeSort(int* arr,int size)       //归并排序
{
	int* tmp = (int*)malloc(sizeof(int)*size);
	if (tmp == NULL)
		return;
	_MergetSort(arr, 0, size);
	free(tmp);

}



`

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值