C语言排序

插入排序:

理念:在数组中,把一个数插入对应的位置,使得这个数组依然有序。

第一次进来 end =0;

判断 i 大于 tmp吗?不大于break , i+1= tmp ,不变

end++等于1 ,i 等于1,tmp=a[end+1]

i 大于 tmp , i + 1 赋值为 i , i 减减

再看 i 大于 tmp ,i + 1 = i , i减减

此时 i 已经不大于等于 0了,所以将 i+1 = tmp 

随后 end++等于2,i = end,tmp =end+1(那个下标的元素).

由于没有大于的,所以end++等于3,i = end, tmp = end+1;

end=3也没有大的,我们直接来到 end=5

i 大于tmp,i + 1= i;i - -;

 i 大于tmp i + 1 = i ; i - -;

i 不大于 tmp break,i + 1 = tmp;end++,i = end ,tmp = end+1;

i > tmp , i + 1 = i ; i - - ;

下面就是不大于将8 放入 i+1的位置,依次往后走就行。

void InsetSort(int* a, int n)//插入排序
{
	int end = 0;
	int tmp = 0;
	for (end; end < n-1; end++)//end为确定的坐标
	{	int i = end;//利用一个i 去指向end及左边的坐标
		tmp = a[end + 1];//保存end后一个的值
		for (i; i >= 0; i--)//end及以前的值与end+1进行对比
		{
			if (a[i] > tmp)//如果前面的某个值大与end+1
			{
				a[i + 1] = a[i];//就将比end+1大的往前挪

			}
			else
				break;//如果提前遇见我们就结束循环,
					  //这样下面的代码就能同时解决两种情况
		}
		a[i + 1] = tmp;//如果说end及前面的都比end+1大,挪动完了之后,
					   //i最后会等于-1,此时相当于将end+1的元素放在第一位。
	}

}

希尔排序

因为有gap的存在,使得当gap不为1的时候,能够预排序使得接近与有序,gap越大中间跳过的参数越多,也就越不准,当gap为1的时候就是插入排序了,此时,里面大概率已经很有序了。

当end = 0, i = 0 ; 那么 i 是否大于 end + gap,大于,因为已经提前保存了 end +gap,

所以将 9,挪到 6的位置,随后i -= gap等于 -3,跳出循环,将 i +gap位置放入tmp(也就是end +gap),

end = 1,与上面一样

end = 2,也是一样

end = 3,这就不一样罗,首先保存 3 ,因为 9 大于 3,所以把 9 挪到 3的为位置随后

i -= gap等于0,

小差错,那个end 忘记调位置了,应该是在 空格位置

再比较 6 与 3 的大小,大于往前挪,此时i -= gap等于 -3跳出循环,可以将 3 放入 6的位置了。

end = 4,8 和 5 都大于2,先将 8 挪到 2 的位置,再将 5 挪到 8 的位置,随后将 2 放入 5

完成后的顺序

顺序就是这么调整的,可参考图片对应代码食用。

void ShellSort(int* a, int n)//希尔排序
{
	int gap = n;//gap为确定下标的后gap个元素
	for (gap; gap > 1; gap = gap / 2)
	{
		int end = 0;//确定下标的变量,每次gap刷新时下标再次来到0
		for (end; end < n - gap; end++)
			//end < n - gap,当end走到n-gap-1时
			//下方的tmp刚好等于最后一个元素,避免了越界		
		{
			int tmp = a[end + gap];//把确定下标的后gap个元素保存
			int i = end;//拷贝一份end坐标
			for (i; i >= 0; i -= gap)//把end以及前面end-gap的元素与end+gap的元素作比较
			{
				if (a[i] > tmp)//如果大就往前挪动
				{
					a[i + gap] = a[i];
				}
				else
					break;//提前遇见终止
			}
			a[i + gap] = tmp;
		}

	}
		
}

 堆排序

堆的结构

逻辑结构体是一个完全二叉树,物理结构体是数组

        在一个数组里面,但是你得想象成是一个二叉树,根(下标为0)后面的两个元素是左子树(下标为1)和右子树(下标为2),到第四个(下标为3)和第五个(下标为4),就是第二个元素(下标为1)的左右子树,第六第七就是第三个元素(下标为2)的左右子树。

5,3就是4的左右子树,6,2就是5的左右子树,7,3就是3的左右子树。

        下面为逻辑结构的样子。上面的是物理结构。

通过根找左右子树的的公式:

        下标乘2再加一,为左子树下标

        下标乘2再加二,右子树下标

通过左右子树找,根节点公式:

        下标减一再除二,因为计算机的计算特性,比如,上图的 2(下标为3) 和 6 (下标为4),那么他们的根节点 5 (下标为1),不管你是 3 - 1除2,还是4 - 1除2,结果都为1.如果不一样那么说明根节点不一样。

       

向下调整算法:

        目前是大堆,小堆的话只需要将红线中的符号变大于,运行逻辑在建堆那里。或者看注释

void Adjustdwon(int *a,int n,int root)//向下交换,特征是root 的左右子树都是满足大堆或者小堆的
{
	int parent = root;//parent 和 smallchild 均为下标
	int smallchild = 0;
		while (parent <= (n-1-1)/2)//父节点不超过最后一个度不为0的节点
		{
			//如果父节点的右子树在数组范围内,并且左子树小于右子树
			if (parent * 2 + 2 < n && a[parent * 2 + 1] < a[parent * 2 + 2])
				smallchild = parent * 2 + 2;//那么将孩子节点赋值为右子树
			else
				smallchild = parent * 2 + 1;//如果说父节点的右子树不在范围,或者左子树大于右子树
											//孩子节点赋值为左子树
			//只要进入循环了,则表示父节点一定是有效的节点,一定有左右子树

			if (a[parent] < a[smallchild])//如果父节点的元素小于孩子则交换
			{
				int tmp = a[parent];
				a[parent] = a[smallchild];
				a[smallchild] = tmp;
				parent = smallchild;//将孩子节点赋值给父节点,这是为了能继续向下查找,
									//直到parent节点不在有效节点范围内,或者提前遇见了更大的数
			}
			else//否则跳出循环,因为我们默认父节点的左右子树是满足大小堆的
				break;
		}

}

建堆

分为大堆和小堆:
        大堆:根大于左右子树

        小堆:根小于左右子树

那么如何建呢?使得一个乱序的数组变成大小堆

        以这幅图为列子,找到最后一个子树的根(绿色的3),让根与子树之间比较,小于就交换,然后去看绿色的7(这个去看的步骤为坐标减一),当我们的倒数第二层走完后,表示这一层都满足了根比子树小,只需要找到最后一个不为叶子的子树,然后判断交换,完了之后下标 减一,一直减到0下标,就是最后一次判断了。

一定要循环判断下去

如果我们将,最上面的3改成10,

OK我们找到最后一个节点的根绿3,将他与7交换,

随后绿3- -,来到绿7,绿7与0交换,

减减,最上面是10,与0交换,10可不小7或者9,所以最好是能再次判断10与7 9的大小。

不直接将 绿3 改成10的原因就是有可能他是可以满足的。

调用上面的向下查找算法,从最后一个有效节点开始,就能实现建堆

排序

建完堆之后,将首尾交换,然后进入查找里面再找一个最大的数放在顶端,然后范围减一,继续交换,当范围等于1的时候,意味着还有两个数,这两个数交换之后就不用再进去找大数字放顶端了。

原因是:当范围是2的时候,还有三个数,一个根两个子节点,进来第一数就和第三个数交换,再 end 变成 2 之前,这三个数已经选出了最大数,所以进来交换,然后去看剩下的两个数谁大

end 还是 2 ,但是走到函数里面,右子树已经在范围内了,因为此时的右子树,是已经排好序的较大值。所以剩下 0,1比较,如果 “ 大 ” 会交换没有跳出。

此时,0 下标的数一定是大于 1 下标的数的。end - -等于1,while 循环条件满足,0 ,1交换,交换完之后所有数都有序了。不用再进去找最大数了。

        那个If 语句也可以写成,如果等于1直接break,就行,不等与就进入

测试方法

双手请放在横向的数字键盘区,也就是字母区上面的数字区,然后快速的随便乱按,在那个数组里面。

然后用逗号将他们分开 

最后运行检验结果,不放心的可以多试几次

直接选择排序

逻辑:两个变量,一个用来找最大值的坐标,一个用来找最小值的坐标,找出来之后,放在头和尾,随后头尾的坐标向中间靠拢。

有可能遇见起始坐标是最大值的情况,此时如果先将小的值放过去那么最大值就被覆盖了,即使是先换大值也有能遇见尾坐标是最小值,也会覆盖。两种情况选一种处理就行。

head 和end用来表示范围,min , max 最大最小值的下标,i 用来遍历

进入  i  的循环,i 小于 min 吗(i 下标和 min 下标的元素)?不小于,也不大于 max。

i++;

i 小于 min 吗?不小于同时也不大于

i++;

i 不小于 min 但是大于 max 所以 max = i(赋值的是下标);然后 i++;

i 不小于 min 也不大于max ,i++;

 i 大于 max ,max = i; i++;

i 大于max , max = i ; i++;

max 已经是最大值了(就我们凭肉眼观察来说)。后面我就直接跳过使其 min 找到最小值。

之后 的 i++就没有操作了。来到交换,交换玩缩小范围

一般过程就是上述这样,接下来说一下特殊情况也就是这一段代码的由来

如果说确定 max 等于 head, end 等于 min ,ok 我们交换

head 与 min 先交换

然后, end与Max交换,就又交换回来了

所以当 head等于 Max 的时候,head 与 min  交换,此时min 指向的元素就是最大的那个 ,所以我们 max = min,就能解决。 min 不等于 end 也能解决

//直接选择排序
void SelectSort(int *a , int n)
{ 
	int head = 0;//最小值放入的位置
	int end = n - 1;//最大值放入的位置
	while (head < end)
	{
		int i = head;
		int min = head;
		int max = end;
		while (i <=end)
		{
			if (a[i] < a[min])//i一直往后走,如果某个下标为i的元素小于第一个数,min = i;						    
			{
				min = i;//此时min 是当前最小值坐标
			}
			//这两个 if 语句只会进来一个,说明当 i 既不小于最小的,又不大于最大的他就是中间值
			//不用执行任何操作
			if (a[i] > a[max])//如果 i 大于 max ,max 就是该坐标
			{
				max = i;
			}
			i++;//可不能忘了写,要不然就死循环了
		}
		{//最小值交换
			int tmp = a[head];
			a[head] = a[min];
			a[min] = tmp;
		}
		//存在一个问题就是,head为最大值,交换完后,head 最小值
		//此时max就是最小值,再交换的话,就将最小值移到最后面了
		//head==max
		//end == min;
		if (head == max)
			max = min;
		{//最大值交换
			int tmp = a[end];
			a[end] = a[max];
			a[max] = tmp;
		}
		{//缩小范围
			head++;
			end--;
		}
	}
}

快速排序

挖坑法

目标数组

函数主体

代码运行:

只讲一趟排序,将第一个 key 值放入正确的位置

对 挖坑函数调用,给他传首元素以及尾元素的地址

left = 0 ,right = 13

判断,left > = right ?,表达式为假,跳过

pivot(坑的坐标) = 0; key  = 8 ; head = 0 ;end = 13;

进入外层循环  0(head) < 13(end),表达式为真

0 (head)< 13(end) && 9(a[end]) >= 8(key)?表达为真,end- -;

再次判断 0(head) < 12(end) && 7(a[end]) >= 8(key)?表达为假,循环结束;

将 7 挪到 8的位置,然后pivot(坑)的坐标为 12

0(head) < 12(end) && 7(a[head]) <= 8(key)?表达式为真,head++;

再次判断 1(head) < 12(end) &&  3(a[head]) <= 8(key) ,表达式为真 ,head++;

再次判断 2(head) < 12(end) && 7(a[head]) <= 8(key)表达式为真, head++

再次判断 3(head) < 12(end) && 4(a[head]) <= 8(key); 表达式为真head++

再次判断 4(head) < 12(end) && 9(a[head]) <= 8(key);?表达式为假 ,挪动 ,pivot  = head;

head 判断结束,看一下 head与end 的关系,4 < 12表达式为真,进入

4(head) < 12(end) && 9(a[end]) > 8(key)  ?表达式为真 ,end--;

再次判断, 4(head) < 11(end) && 5(a[end]) > 8(key) ? 表达式为假,循环跳出

挪动 a[end ], pivot  = end;

判断 4(head) < 11(end) && 5(a[head]) <= 8(key);?表达式为真,head++;

 

判断 5(head) < 11(end) && 5(a[head]) <= 8(key);?表达式为真,head++;

判断 6(head) < 11(end) && 7(a[head]) <= 8(key);?表达式为真,head++;

判断 7(head) < 11(end) && 2(a[head]) <= 8(key);?表达式为真,head++;

判断 8(head) < 11(end) && 3(a[head]) <= 8(key);?表达式为真,head++;

判断 9(head) < 11(end) && 4(a[head]) <= 8(key);?表达式为真,head++;

判断 10(head) < 11(end) && 9(a[head]) <= 8(key);?表达式为假,执行

判断10(head) < 11(end)为真进入

判断

10(head) < 11(end) && 9(a[end]) >= 8(key) , 表达式为真,end --;

11(head) < 11(end) && 9(a[end]) >= 8(key) , 表达式为为假,执行

由于 pivto 和 end 在一个位置,不用交换了。

11(head) < 11(end) && 9(a[end]) >= 8(key) , 表达式为为假,执行

由于 pivto 和 head 在一个位置,不用交换了。

最后,表达式为假,外层循环也结束了。

将 关键字放在坑的位置

这样关键字的坐标都是小于等于关键字的值,右边的都是大于等于关键字的。所以如果关键字相同数太多,第一趟结束后,你去观察,如果你不知道哪一个是具体的关键,可能会发现主观上的不对,其实是对的。

然后说一下区域的划分

左区域为

右区域为

依次递归下去,直到这俩相等就开始返回

交换法

逻辑:head找大,end 找小,找到了之后,head 和 end 交换。

当他俩相等时,将 pivot 对应的值放入 他俩当中的任意一个坐标当中。

代码运行

 这一把咱们加个8 进去

 

直接跳过一些步骤,直到左为大,右为小停止

然后对他俩交换

交换完之后,head  < end 再次右找小,左找大,代码的顺序是,先右后左

然后交换

然后end--与head ,end = head ,他们俩再交换不变,相遇跳出循环

随后 pivot 与 head再交换

然后和挖坑法一样再分区域递归下去

双指针法

判断,a[cur] 3 <  a[pivot ] 8 && ++prev != cur ,前一个表达式满足,可 ++prev 等于 cur,不进入判断语句,然后

上来又判断 

进来之后再次判断 

7 < 8 && 2 != 2 表达式为假,cur++

cur <= right 为真

4 < 8 && 3 != 3表达式为假  cur++;

cur < = right 为真

到了这里 ,先判断的 9 < 8为假此时后面的 ++ prev则不会执行了。

所以只会 cur++;

cur <= right 

5 < 8 && 4 != 5 表达式位置呢执行,交换prev 和cur 里面的值

然后 cur ++;

操作为 prev++;

交换 

交换完之后 cur ++;

 cur <= right 

7 < 8 && 5 != 6,表达式为真 ,9  与 7交换 ,随后 cur ++;

cur <= right 

2 < 8 && 6 != 7,表达式为真,9  与 2 交换 cur++;

cur <= right 

8 < 8  表达式为假  cur ++

cur <= right 

4 < 8 && 6 != 9 ,交换 cur ++;

cur <= right 

9 < 8 表达式为假 cur ++;

cur <= right 

5 < 8 && 8 != 11 ,表达式为真 ,交换  cur++;

cur <= right 

7 < 8 && 9 != 12 ,交换 cur++;

cur <= right 

9 < 8 表达式为假 cur ++;

cur <= right 

9 < 8 表达式为假,cur++;

cur <= right 表达式为假 外层循环结束,交换 prev 与 关键值pivot 

交换

最后来个逻辑推理:

根据这一段代码可知,每次发生交换的情况是 cur 指向的值小于 pivot 指向的值,cur 往前交换,能使得 prev 之前的值都小于 pivot 。最后 prev与pivot 交换,就能使得,关键值(pivot )之前的值都是小于,不用担心,最后 pivot 与 prev ,交换时,prev大于关键字 ,因为那个值经过 cur 的验证的,小于 prev才会指向

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值