排列树,子集树

文章介绍了如何在部分有序的循环数组中使用二分查找法找到最小元素,通过比较左、中、右指针确定查找范围,并提供了代码实现。接着,文章讲解了利用分治思想实现排列树生成全排列的方法,以及构建子集树来表示集合的所有子集。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上期给各位宝子们分享了分治策略和二分查找,最后给大家留了一个问题就是:有序循环数组的二分查找。我们上硬货

目录

有序循环数组的二分查找

思路:

代码实现

排列树

 代码实现

分析:

子集树

分析

代码实现


有序循环数组的二分查找

有这样一个从小到大有序的数组,现在我们改变条件:让他部分有序

在这样的一个数组当中我们找出最小的元素的位置,使用二分查找。

同理最大的也是这个思路

思路:

首先我们先来判断左指针和右指针指向的元素大小,如果左小于右说明我们这个数列是完全有序,返回arr[left]即left指针指向的这个元素就是我们要找的最小元素

左大于右:(我们找到中间元素,利用中间元素与左指针和右指针的大小来进行查找)

由上图可知,5>4所以我们找到中间指针:9

1.我们来判断中间指针和左右两个指针,9>5,因为部分有序所以5到9这个范围我们就不找了,最小元素肯定不在这个里面,所以我们的左指针指向mid+1的位置

2.left指向10,right指向4,mid指向2

3.因为中间指针数据比右指针的数据小,所以从中间指针到右指针这个范围我们就不用找了,我们继续将规模缩小,right指针指向mid-1

此时:左指针和中间指针相等,这就找到我们最大的元素了,所以我们left继续向右偏移,这个时候我们就找到我们最小元素所在位置了

代码实现

int  funa(int* ar, int left, int right) {
    if (ar[left] < ar[right]) {
        return left;
    }
    while (left <= right) {
        int mid = left + ((right - left) >> 1);
        if (ar[mid] >= ar[left]) {
            left = mid + 1;
        }
        else if (ar[mid] < ar[right])
        {
            right = mid - 1;
        }
        if (ar[left] < ar[right]) {
            return left;
        }
    }
}
int main() {
    int ar[9] = { 5,6,7,8,9,1,2,3,4 };
    int i = funa(ar, 0, 8);
    printf("最小的数在第%d位置", i);
}

注意:

1.条件:left<=right:如果去掉等号只适合数组总元素个数是奇数的,而偶数就会报错,原因就是我们偶数的时候left指针和right指针和mid指针在一个数据上,而奇数的时候我们left指针和right指针差一个

好了接下来进入正题--->排列树

排列树

问题描述:数组{1,2,3}可以排列成{1,2,3},{1,3,2},{2,1,3},{2,3,1},{3,1,2},{3,2,1}这几种结果,我们现在利用分治思想来实现

思路

首先:我们先确定第一个元素:1,2,3三种情况

其次:确定完首元素后,第二个元素(我们以首元素为1为例):2,3情况

最后:确定完前两个元素,第三个元素:就剩下2异或3,一种情况

这样我们通过层层削减问题规模来达到全排列

 代码实现

#include<stdio.h>
#include<iostream>
void Perm(int* nums, int k, int m) {
	if (k == m) {
		for (int i = 0; i <= m; i++) {
			printf("%5d", nums[i]);
		}
		printf("\n");
	}
	else {
		for (int i = k; i <=m; i++) {
			std::swap(nums[i],nums[k]);
			Perm(nums, k + 1, m);
			std::swap(nums[i], nums[k]);
		}
	}
}
int main() {
	int nums[] = { 1,2,3 };
	int lenth = sizeof(nums) / sizeof(nums[0]);
	Perm(nums, 0, 2);

}

分析:

如图所示,在这个数组中我们定义两个变量:一个指向第一个元素,一个指向最后一个元素,当第一个元素和最后一个元素相等(即k==m)说明我们数组只有一个元素,这个时候我们打印即可,无需排列

当k!=m时,我们先进行首元素排列

这次递归完打印{1,2,3}

swap函数主要的作用就是某个位置上的元素的选择,如第一位置可以是1,2,3,我们通过交换函数将2移动到第一个位置,这个时候就能实现首元素是2的要求,但是交换完我们必须再交换一次。

通过i的变化让我们确定接下来元素,如确定完第一个位置是1,{2,3},{3,2}两种可能这个时候就需要i这个变量来控制接下来元素的确定

子集树

我们都学过集合,小白来考考大家,{1,2,3}这个集合子集是?

答案是:{1,2,3},{1,2},{1,3},{2,3},{1},{2},{3},{空集}

分析

 我们先来看这棵树,类比哈夫曼树编码特点,让左子树为1右子树为0,1为存在,0为不存在,通过这棵树我们可以得到子集

 

 

代码实现

void func(int* arr, int* brr, int i,int n) {
	if (i == n) {
		for (int j = 0; j < n; j++) {
			if (brr[j] == 1) {
				printf("%5d", arr[j]);
			}
			
		}
		printf("\n");
	}
	else {
		brr[i] = 1;
		func(arr, brr, i + 1, n);
		brr[i] = 0;
		func(arr, brr, i + 1, n);
	}

}

int main() {
	int arr[] = { 1,2,3 };
	int brr[3] = { 0 };
	func(arr, brr, 0, 3);
}

以上就是今天的所有内容啦,希望各位宝子能够喜欢

小白会继续努力的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值