考研:选择排序(简单选择排序、堆排序)

简单选择排序


算法思想:假设是一个有 n 个元素的数组,假设升序

  1. 第一步,从 1 到 n 中,依次比较选择一个最小值,放在第一个位置。
  2. 第二步,由于第一个元素已经是最小值,所以这时候我们从 2 到 n 中,选择一个次小值,放在第二个位置。
  3. 依次类推,直到在会剩下两个元素为止,进行最后一次判断后,就可以得到一个有序的数组了。

代码如下:

// 两个参数:待排序数组,和数组元素个数
// 假设我们要升序
void SelectSort(int arr[], int n){
	
	// 定义两个用于循环的变量
	int i, j;

	// 外层循环控制比较的轮数,最后一个元素不需要比较,所以只需要n-1次比较
	for (i=1; i<=n-1; i++){

		// 从i到n-1进行比较,因为前面i-1轮已经找出了i-1个最小值
		// 因为是相邻的比较,所以第n-1次比较的时候,比较的第n-1个元素和第n个元素的大小
		for (j=i; j<=n-1; j++){

			// 如果当前元素比它的后一个元素大,那么就交换位置,让大的往后走
			// 如果当前元素不必后一个元素大,就不用交换,也实现了大的元素在后面
			if (arr[j]>arr[j+1]){
				arr[0] = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = arr[0];
			}
		}
	}
}


堆排序

【大顶堆=大根堆,小顶堆=小根堆】

堆排序思想:

  1. 把数组想象成一颗完全二叉树,让这颗树满足根节点大于(小于)左右孩子,然后左右子树也满足这个条件。
  2. 最终建立而成的完全二叉树就是一个大顶堆(小顶堆)。这时候在最上面的根部的元素一定是这棵树的最大值(最小值)。
  3. 而且由于这是一个用数组模拟的完全二叉树,所以最上面的根部元素一定是数组的第一个元素,堆底元素一定是最下面一排的最右边的元素。
  4. 那么我们让根元素(数组第一个元素)和堆底元素(数组最后一个元素)交换,就可以把筛选出来的最值放在后面。
  5. 这时候由于根部元素是从堆底上去的,就不满足大顶堆(小顶堆)的要求,然后我们再重新对除了最后一个元素的剩下元素进行建堆操作。
  6. 再次建堆后,根部元素就是剩下元素的最大值,然后再让根部元素后剩下元素的最后一个元素交换,我们就得到了两个最值的有序序列。
  7. 然后不断重复以上的建堆然后交换的操作,直到最后只剩下一个元素,这个数组整个就是一个有序序列了。

堆排序代码思路:

  1. 首先,我们得知道,对于一个完全二叉树,有孩子的节点是前 n/2 个元素后面的都是叶子节点,叶子节点是自然服从顶堆规则的,因为只有一个嘛。(关于这点大家可以自己画图验证一下)
  2. 先写一个建堆的函数
  3. 然后进行堆顶元素和堆底元素的交换
  4. 然后再对剩下的元素进行建堆操作
  5. 重复 2 3 直到只剩下一个元素为止

// 1. 调整为堆函数:假设建立大顶堆
// 三个参数,arr是待建堆的数组
// 假设 arr[k+1 ... n]已经是堆,将arr[k ... n]调整为以s为根的大顶堆。
void HeapAdjust(int arr[], int k, int n){

	// 用于循环的变量
	int i;

	// 把当准备建堆的第k个元素放入0的位置暂时存着
	arr[0] = arr[k];

	// i=2*k就是k节点的左孩子,取右孩子只需要左孩子+1
	for (i=2*k; i<=n; i*=2){
		// 在i还在n这个节点范围内时,如果左孩子比右孩子小,说明右孩子是较大值
		if (i<n && arr[i]<arr[i+1]){
			
			// 让i+1后,这时候就是指向了右孩子(较大值)
			i=i+1;
		}

		// 如果当前k节点的值小于他的孩子结点
		if(arr[0]<arr[i]){

			// 就把他的孩子结点的值赋值给k节点
			// 这里不用担心覆盖,因为在arr[0]中存了一份,而且上面判断也是用的arr[0]
			arr[k] = arr[i];

			// 然后让k节点赋值为i,也就是间接的让k节点的值走到了i节点
			// 下次循环就是判断的i节点的左右孩子,依次反复
			k = i;
		}else{

			// 如果上面的结论没有成立,说明k节点比他的左右孩子都大
			// 并且他的左右孩子都是之前已经建堆完成的,所以就没必要再进行循环了,直接退出循环
			break;
		}
	}

	// 整个循环结束后,由于k通过k=i进行了移动,所以我们把最开始存好的arr[0]值赋值给新的k的位置
	arr[k] = arr[0];
}

// 2. 初始化堆
void BuildHeap(int arr[], int n){

	// 循环变量
	int i;

	// 因为前n/2个元素才是分支节点,后面的都是叶子,所以这里从n/2到1依次建立堆
	// 从下往上有个方便的地方就是下面已经符合堆,就只需要慢慢往上面解决就行
	for (i=n/2; i<=1; i++){
		
		// 通过堆调整,对每个分支节点进行依次调整
		HeapAdjust(arr, i, n);
	}
}

// 3. 堆排序
void HeapSort(int arr[], int n){

	// 循环变量
	int i;

	// 先把数组初始化一个堆
	BuildHeap(arr, n);

	// 然后通过for循环依次调换堆底和堆顶的元素,让最值放在最后
	for (i=n; i>=2; i--){
		
		// 交换堆顶和堆底的元素
		arr[0] = arr[i];
		arr[i] = arr[1];
		arr[1] = arr[0];

		// 交换完了后,就对剩下的元素进行堆调整,便于下次交换
		HeapAdjust(arr,1, i-1);
	}
}


主函数

void main(void){
	int i;
	int arr[] = {0,135,4,315,3,15,43,54};
	int n = 7;
	HeapSort(arr, n);
	for (i=1; i<=n; i++){
		printf("%d,", arr[i]);
	}
	printf("\n");
}

       以上的两个排序,堆排序较难,如果暂时理解不了堆排序的代码,可以先理解堆排序的思想
       大家如果有什么特别的见解,欢迎评论留言。 ~. ~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为什么我不是源代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值