PAT甲级技巧总结(一)

1.判断堆是大顶堆还是小顶堆

int a[1009], isMin = 1, isMax = 1;  //数组a存储堆(完全二叉树),第一个节点的索引为1
//isMin标识是否为小顶堆  isMax标识是否为大顶堆
for (int i = 2; i <= n; i++) {  //和父结点比较value
	if (a[i/2] > a[i]) isMin = 0;  
	if (a[i/2] < a[i]) isMax = 0;
}
if (isMin == 1)
	printf("Min Heap");
else
	printf("%s", isMax == 1 ? "Max Heap" : "Not Heap");
	//若isMin和isMax都为1,则不是堆

2.深度遍历堆

vector<int> v; //用于存储深度遍历从根到叶子结点的路径
void dfs(int index) {  //此处遍历方式为右->根->左
	if (index * 2 > n && index * 2 + 1 > n) {  //该结点为叶子结点
		if (index <= n) {
			for (int i = 0; i < v.size(); i++)
				printf("%d%s", v[i], i != v.size() - 1 ? " " : "\n");
		}
	} else {
		if(index * 2 + 1<=n){
            v.push_back(a[index * 2 + 1]);
            dfs(index * 2 + 1);
            v.pop_back();
        }  
        if(index * 2 <=n) {
            v.push_back(a[index * 2]);
            dfs(index * 2);
            v.pop_back();
        }
	}
}

3.判断是否为素数

bool isPrime(int n) {
	if (n == 0 || n == 1) return false;
	for (int i = 2; i * i <= n; i++)
		if (n % i == 0) return false;
	return true;
}

4.最低公共祖先

已知某个树的根结点,若a和b在根结点的左边,则a和b的最近公共祖先在当前子树根结点的左子树寻找,如果a和b在当前子树根结点的两边,在当前子树的根结点就是a和b的最近公共祖先,如果a和b在当前子树根结点的右边,则a和b的最近公共祖先就在当前子树的右子树寻找。

map<int, int> pos;  //记录中序遍历中每个结点的位置以便比较和根结点的位置关系
vector<int> in, pre;  //记录中序遍历和先序遍历
void lca(int inl, int inr, int preRoot, int a, int b) {
	if (inl > inr) return;
		//找出当前根结点和两个结点的位置,然后进行比较
		int inRoot = pos[pre[preRoot]], aIn = pos[a], bIn = pos[b];
	if (aIn < inRoot && bIn < inRoot)
		lca(inl, inRoot-1, preRoot+1, a, b);
	else if ((aIn < inRoot && bIn > inRoot) || (aIn > inRoot && bIn < inRoot))
		printf("LCA of %d and %d is %d.\n", a, b, in[inRoot]);
	else if (aIn > inRoot && bIn > inRoot)
		lca(inRoot+1, inr, preRoot+1+(inRoot-inl), a, b);
	else if (aIn == inRoot)
		printf("%d is an ancestor of %d.\n", a, b);
	else if (bIn == inRoot)
		printf("%d is an ancestor of %d.\n", b, a);
}

对于二叉搜索树的最低公共祖先则更加容易。二叉搜索树具有显著的特点,即根结点的值大于等于左子树结点的值,小于右子树结点的值。所以只需要知道先序遍历序列,然后从前往后查找某一个结点的值位于两个给定的结点的值之间,则必为最低公共祖先。因为先序遍历的特点,所以从前向后遍历每个结点等同于从上到下对每个根结点进行测试(即先序遍历的特点)。

vector<int> pre(n); //存储先序遍历序列
for (int i = 0; i < m; i++) {
        scanf("%d %d", &u, &v);
        for(int j = 0; j < n; j++) {
            a = pre[j];
            if ((a >= u && a <= v) || (a >= v && a <= u)) break;
        }
        if (mp[u] == false && mp[v] == false)
            printf("ERROR: %d and %d are not found.\n", u, v);
        else if (mp[u] == false || mp[v] == false)
            printf("ERROR: %d is not found.\n", mp[u] == false ? u : v);
        else if (a == u || a == v)
            printf("%d is an ancestor of %d.\n", a, a == u ? v : u);
        else
            printf("LCA of %d and %d is %d.\n", u, v, a);
    }

5.判断环和简单路径(环)

如果给出的路径存在某两个连续的点不可达或者第一个结点和最后一个结点不同或者这个路径没有访问过图中所有的点,那么它就不是一个环路(flag = 0)~如果满足了环路的条件,那么再判断这个环路路否是简单旅环路,即是否访问过n+1个结点(源点访问两次)

vector<int> list; //存储走过的结点
set<int> s; 
 //用以记录访问过的结点,利用了set元素不重复的性质,最后查看set元素个数即可知道是否所有结点都访问过
for(int j=0;j<num-1;j++){
        if(e[list[j]][list[j+1]]==0){ 若存在结点之间不可达则不是环路
            flag=0;
        }
		sum += e[list[j]][list[j+1]];  //记录边权值加和
    }
    if(flag==0){
        printf("Path %d: NA (Not a TS cycle)\n",k+1);
    }else if(s.size()!=n || list[0]!=list[num-1]) //没有全部走过一遍或者最后一个结点和初始结点不一样
    {
		printf("Path %d: %d (Not a TS cycle)\n",k+1,sum);
	} 
    else if(num  != n+1) //全部走过了一遍,但是多走了若干个结点
    {
		printf("Path %d: %d (TS cycle)\n",k+1,sum);
            if(sum < ans){  //找出最短路径的边权值之和和路径的编号
	            ans = sum;
	            ansid = k+1;
        	}
    }else{
        printf("Path %d: %d (TS simple cycle)\n",k+1,sum);
        if(sum < ans){
            ans = sum;
            ansid = k+1;
        }
    }

6.完全二叉树叶子结点、非叶子结点个数

一个具有n个节点的完全二叉树,其叶子节点的个数n0为: n/2 向上取整,或者(n+1)/2 向下取整
推导:n0+n1+n2 = n , n1+2n2=n-1 又n1=0或1
当n=1时(n为偶数),n0 = n/2 当n=0时(n为奇数),n0 = n+1/2
综上所述 叶子节点的个数n0为: n/2 向上取整 非叶子结点个数为n/2 向下取整

7.完全二叉树层次遍历——堆,判断是大顶堆还是小顶堆的第二种方法

//完全二叉树的层次遍历就是堆的静态存储结构
//堆的后序遍历,此处堆的存储索引从0开始
//左子树根结点的索引为2*i+1,右子树根结点的索引为2*i+2
void postOrder(int index) {
	if (index >= n) return;
	postOrder(index * 2 + 1);
	postOrder(index * 2 + 2);
	printf("%d%s", v[index], index == 0 ? "\n" : " ");
}

//此处判断是否为堆的思路稍微不同,判断每个结点的左右子树的根结点与当前结点的值的关系。
int flag = v[0] > v[1] ? 1 : -1;  //根据根结点和左孩子的大小关系判断为大(小)顶堆
for (int j = 0; j <= (n-1) / 2; j++) {  //遍历所有非叶子结点个数
		int left = j * 2 + 1, right = j * 2 + 2;
		if (flag == 1 && (v[j] < v[left] || (right < n && v[j] <v[right]))) 
			flag = 0;
		if (flag == -1 && (v[j] > v[left] || (right < n && v[j] >v[right]))) 
			flag = 0;
		}
		if (flag == 0) printf("Not Heap\n");
		else printf("%s Heap\n", flag == 1 ? "Max" : "Min");
}

8.测试序列是否为拓扑序列

vector<int> v[1010];  //邻接表 
int in[1010];   //入度数组
int flag=0;  //flag用于标识是否为第一个输出 
for(int i=0;i<k;i++){
		vector<int> tin(in,in+n+1);  //创建入度数组的副本
		int judge=1; //标识是否为拓扑序列 
		for(int j=0;j<n;j++){
			scanf("%d",&a);  //输入结点进行判断
			if(tin[a]!=0) judge = 0;   //输出结点的入度不为0,表示不为拓扑序列
			for(int it : v[a]) tin[it]--;
		}
		if(judge==1) continue; //为拓扑序列不输出,不为拓扑序列输出编号
		printf("%s%d",flag==1?" ":"",i);  //flag==0表示第一次输出
		flag = 1;
}

9.哈希表

bool isprime(int n) {  
    for (int i = 2; i * i <= n; i++)
    if (n % i == 0) return false;
    return true;
}
//size为哈希表容量大小,找寻大于等于size的最小质数做哈希函数中的tsize[H(key) = key % tsize]
while(!isprime(tsize)) tsize++;
 
 //将数据插入
 vector<int> v(tsize);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a);
        int flag = 0;
        for (int j = 0; j < tsize; j++) {
            int pos = (a + j * j) % tsize;  //平方探测的j为 0 ~ tsize-1
            if (v[pos] == 0) {
                v[pos] = a;
                flag = 1;
                break;
            }
        }
        if (!flag) printf("%d cannot be inserted.\n", a);
    }
//查找
int ans = 0;  //记录查找次数
    for (int i = 0; i < m; i++) {
        scanf("%d", &a);
        for (int j = 0; j <= tsize; j++) {  //查找时j为 0 ~ tsize
            ans++;
            int pos = (a + j * j) % tsize;
            if (v[pos] == a || v[pos] == 0) break;
        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值