看数据结构写代码(58) B-树

B-树 一种 自平衡的 多路 查找树。它在文件系统里很有用。

一个m阶的B-树,要么是空树,要么是满足这些特性的树。、

1.树 最多 有 m个分支。

2.树的根 最少 两个子树。

3. 树的非终端叶子节点 最少  m/2 向上 取整   个 子树。

4.所有叶子节点 都在 一层。


它的节点 结构:  (N,P0,K1,P1,K2,p2......Kn,Pn)

其中 N 是 节点的 关键字个数,p0~pn 是 指向子树的指针,K1~Kn是关键字


#define M		3 //B树的阶数..

typedef int keyType;

typedef struct BTreeNode{
	int keyNum;//关键字的个数.
	BTreeNode * ptr[M+1];//指针数组,(总比 关键字 多一个).
	keyType key[M+1];//关键字数组,第0个没用,从 第1个开始,
	BTreeNode * parentNode;//节点的父亲..
}*BTree;

根据上面的特性,总结:

1.节点中 分支树,最大为 阶数,最小 为 m/2 向上 取整(根节点最小为2,只有一个根节点 的树 可以没有子树)

2.所有结构中,关键字个数  =  指向子树的指针树 -1,所以关键字 最大 为 M-1,最少 为  上面 所说的 最小值 -1.

3.K1~Kn,顺序排放。

4.假设 k 是 节点的 第 x个 关键字,那么 P(x-1)指向的 子树中的 的 关键字 都比 k小,P(x)指向的子树指向的关键字 都比 k大。


B-树的 查找,分为 两部分,一部分 是顺指针查找节点 ,一部分 是 子 节点里 查找。下面的 search函数 就是 在 子节点里查找 关键字。

struct Result{//查找的结果
	int tag;//0查找失败,1查找成功
	int index;//位置
	BTreeNode * p;//指向查找成功的指针或者插入位置的 指针
};
//查找到:返回k的位置; 没查找到:返回 k值的 搜索指针位置。
int search(BTree t,keyType k){
	if (t != NULL){
		for (int i = 1; i <= t->keyNum; i++){
			if (t->key[i] == k){//找到了返回当前索引
				return i;
			}
			else if(t->key[i] > k){//没找到,返回 前一个位置
				return i-1;
			}
		}
		return t->keyNum;//所有节点 都比 k 小
	}
	return 0;
}
//查找
Result searchBTree(BTree t,keyType k){
	BTree p = t,q = NULL;
	bool isFind = false;
	int index = 0;
	while (p != NULL){
		index = search(p,k);
		if (index >= 1 && p->key[index] == k ){
			isFind = true;
			break;
		}
		else{
			q = p;
			p = p->ptr[index];
		}
	}
	if (isFind){//查找到了
		Result r ={1,index,p};
		return r;
	}
	else{
		Result r ={0,index,q};
		return r;
	}
}

下面 主要 说 B-树的 插入 算法。

1.如果节点 存在,不插入

2.节点不存在,插入 节点

3.如果 插入后, 节点  的 关键字个数 大于 M-1个,则 需要 分裂 ,具体分裂如下:


如果 造成 父节点 关键字个数 超过 M-1,则 需要 分裂。


完整代码如下:

// BTree.cpp : 定义控制台应用程序的入口点。
//B-树:多路自平衡查找树

#include "stdafx.h"
#include <cstdlib>
#include <cmath>
#include "queue.h"
#define M		3 //B树的阶数..

typedef int keyType;

typedef struct BTreeNode{
	int keyNum;//关键字的个数.
	BTreeNode * ptr[M+1];//指针数组,(总比 关键字 多一个).
	keyType key[M+1];//关键字数组,第0个没用,从 第1个开始,
	BTreeNode * parentNode;//节点的父亲..
}*BTree;

//获取一个节点,这样 的 初始化是个 好习惯.
BTreeNode * makeNode(){
	BTreeNode * node = (BTreeNode*)malloc(sizeof(BTreeNode));
	node->parentNode = NULL;
	for (int i = 0; i <= M; i++){
		node->ptr[i] = NULL;
	}
	node->keyNum = 0;
	return node;
}

void initTree(BTree *tree){
	*tree = NULL;
}

struct Result{//查找的结果
	int tag;//0查找失败,1查找成功
	int index;//位置
	BTreeNode * p;//指向查找成功的指针或者插入位置的 指针
};
//查找到:返回k的位置; 没查找到:返回 k值的 搜索指针位置。
int search(BTree t,keyType k){
	if (t != NULL){
		for (int i = 1; i <= t->keyNum; i++){
			if (t->key[i] == k){//找到了返回当前索引
				return i;
			}
			else if(t->key[i] > k){//没找到,返回 前一个位置
				return i-1;
			}
		}
		return t->keyNum;//所有节点 都比 k 小
	}
	return 0;
}
//查找
Result searchBTree(BTree t,keyType k){
	BTree p = t,q = NULL;
	bool isFind = false;
	int index = 0;
	while (p != NULL){
		index = search(p,k);
		if (index >= 1 && p->key[index] == k ){
			isFind = true;
			break;
		}
		else{
			q = p;
			p = p->ptr[index];
		}
	}
	if (isFind){//查找到了
		Result r ={1,index,p};
		return r;
	}
	else{
		Result r ={0,index,q};
		return r;
	}
}
//将 p 插入 到  t->ptr[index+1]
//将 k 插入到 t->key[index+1];
void insert(BTree t,int index,keyType k,BTree p){
	//index 后面的移开
	for (int i = t->keyNum; i > index; i--){
		t->key[i+1] = t->key[i];
		t->ptr[i+1] = t->ptr[i];
	}
	t->key[index+1] = k;
	t->ptr[index +1] = p;
	t->keyNum ++;
}

//将 index 后部分分裂出 一个 新的节点
//指针 复制 :index 到 keyNum
//关键字复制: index +1 到 keyNum
void split(BTree t,int index,BTree * newNode){
	*newNode = makeNode();
	int count = 0;
	for (int i = index; i <= t->keyNum; i++){
		if (i > index){关键字复制: index +1 到 keyNum
			count++;
			(*newNode)->key[count] = t->key[i];//从1 开始.
		}
		BTree child = (*newNode)->ptr[count] = t->ptr[i];
		//忘记写了,需要 把 带走的节点 设置 新的 父节点
		if (child != NULL){
			child->parentNode = *newNode;
		}
		t->ptr[i] = NULL;//设置原有指针为NULL
	}
	//下面两句漏写..
	(*newNode)->parentNode = t->parentNode;
	t->keyNum = index -1;//原节点 数量 减少.
	(*newNode)->keyNum = count;
}
//新的根节点(之前为 空树,或者 分裂出了 一个 头节点)
void newRoot(BTree * t,keyType k,BTree left,BTree right){
	BTree p = *t = makeNode();
	p->key[1] = k;
	p->ptr[0] = left;
	p->ptr[1] = right;
	p->keyNum = 1;
	if (left != NULL){
		left->parentNode = p;
	}
	if (right != NULL)
	{
		right->parentNode = p;
	}
}
//插入
void insertBTree(BTree * t,keyType k){
	Result r = searchBTree(*t,k);
	if (r.tag ==  0){//不存在 ,插入.
		int index = r.index;//插入位置
		BTree p = r.p;//插入节点
		BTree q = NULL;//插入关键字的 右孩子.
		BTree r = NULL;
		bool finished = false;
		while (p != NULL){
			insert(p,index,k,q);
			if (p->keyNum < M){//M阶 的 树,最多 有 M-1个 关键字{
				finished = true;
				break;
			}
			else{//关键字个数 超过M-1个,需要 分裂.
				int mid = (int)ceil(M/2.0);
				split(p,mid,&q);
				k = p->key[mid];//分裂需要插入 到父节点的关键字
				r = p;
				p = p->parentNode;//父节点
				index = search(p,k);//插入到父节点的位置.
			}
		}
		if (finished == false){//空树,或者 根节点的 关键字 大于 M-1
			newRoot(t,k,r,q);
		}
	}
}

//index之后的关键字 和指针左移
void leftMove(BTree p,int index){
	if(index < p->keyNum){
		for (int i = index; i < p->keyNum; i++){
			if (i>= 1){
				p->key[i] = p->key[i+1];
			}
			p->ptr[i] = p->ptr[i+1];
		}
	}
	else{//在最后一个位置,不需要移动
		p->ptr[p->keyNum] = NULL;
	}
	p->keyNum--;
}

//*t 树根,start:搜索开始的节点
bool deleteBTree(BTree * t,BTree start,keyType key){
	Result r = searchBTree(start,key);
	if (r.tag == 1){//找到了
		BTree p = r.p;
		int index = r.index;
		//不是 最底下一层非终端节点,寻找右子树最小节点,替换k值,并删除最小节点
		if (p->ptr[0] != NULL){
			BTree q = p->ptr[index];
			while (q->ptr[0] != NULL){
				q = q->ptr[0];
			}
			p->key[index] = q->key[1];
			return deleteBTree(t,p->ptr[index],q->key[1]);//删除最小节点退出函数
		}
		else// 是 最底下一层 非终端节点.
		{
			//节点最小阶数为 M/2 向上取整,关键字 比 最小阶数 小1
			int min = (int)ceil(M/2.0)-1;
			BTree parent = p->parentNode;
			//是根节点 或者 节点数 大于 min
			if (p->keyNum > min || parent == NULL ){
				leftMove(p,index);//index后面的关键字左移
				if (p->keyNum == 0){//根节点关键字最少为1,空树.
					free(*t);
					*t = NULL;
				}
				return true;
			}
			else if(p->keyNum == min){
				leftMove(p,index);//将删除关键字后面的 左移
				BTree leftBrother = NULL;
				BTree rightBrother = NULL;
				int pIndex = search(parent,key);//寻找节点在 父节点的 位置(查找位置
				if (pIndex>=1){
					leftBrother = parent->ptr[pIndex-1];//左相邻兄弟
				}
				if (pIndex < parent->keyNum){
					rightBrother = parent->ptr[pIndex+1];//右兄弟
				}
				//寻找 是否 有相邻 兄弟 keyNum大于 min的.
				if(leftBrother != NULL && leftBrother->keyNum > min){//左兄弟有空间
					//整个空间 的关键字 右移一个位置
					for (int i = p->keyNum; i >= 1; i--){
						p->key[i+1] = p->key[i];
					}
					p->key[1] = parent->key[pIndex];//父节点关键字下移
					p->keyNum++;
					parent->key[pIndex] = leftBrother->key[leftBrother->keyNum];//将最大值上移到父节点。
					leftBrother->keyNum--;//最终 左兄弟节点的 关键字少了一个,其余没变.
					return true;
				}
				else if (rightBrother != NULL && rightBrother->keyNum > min){//右兄弟有空间
					p->key[p->keyNum+1] = parent->key[pIndex+1];//父节点关键字下移
					p->keyNum++;
					parent->key[pIndex+1] = rightBrother->key[1];//将最小值上移
					leftMove(rightBrother,1);//将 最小值 之后的 左移.
					return true;
				}
				BTree merger = NULL;//合并后的节点
				while (parent != NULL){
					//左右相邻空间都等于 min,需要 删除节点后合并(书中第三种情况)
					if (leftBrother != NULL){
						merger = leftBrother;
						//将节点中其他关键字 和 父节点的第pindex个 复制到左兄弟中
						int lIndex = leftBrother->keyNum+1;//左兄弟下标
						leftBrother->key[lIndex] = parent->key[pIndex];//先复制父节点关键字
						leftMove(parent,pIndex);//父节点关键字后面的左移
						for (int i = 0; i <= p->keyNum; i++){
							if  (i >=1){
								leftBrother->key[lIndex] = p->key[i];
							}
							leftBrother->ptr[lIndex++] = p->ptr[i];
							if (p->ptr[i] != NULL){//设置新的父节点
								p->ptr[i]->parentNode = leftBrother;
							}
						}
						leftBrother->keyNum += p->keyNum + 1;
						free(p);//合并完成删除节点
					}
					else if (rightBrother != NULL){
						merger = rightBrother;
						//加入到右兄弟节点的关键字个数,减去一个关键字,再加上一个父节点关键字
						int addNum = p->keyNum + 1;
						rightBrother->keyNum += addNum;
						//为插入关键字移位
						for (int i = rightBrother->keyNum; i >= 0; i--){
							if (i >= 1){
								rightBrother->key[i+addNum] = rightBrother->key[i];
							}
							rightBrother->ptr[i+addNum] = rightBrother->ptr[i];
						}
						int i = 0;
						for (; i <= p->keyNum; i++){
							if (i >= 1){
								rightBrother->key[i] = p->key[i];
							}
							rightBrother->ptr[i] = p->ptr[i];
							if (p->ptr[i] != NULL){//设置新的父节点
								p->ptr[i]->parentNode = rightBrother;
							}
						}
						rightBrother->key[i] = parent->key[pIndex+1];
						leftMove(parent,pIndex+1);//左移
						free(p);//合并完成,删除节点
					}
					//父节点关键字不足,继续删除父节点,然后合并.
					if (parent->keyNum < min){
						p = parent;
						parent = parent->parentNode;
						pIndex = search(parent,key);//寻找节点在 父节点的 位置(查找位置
						leftBrother = rightBrother = NULL;
						if (pIndex>=1){
							leftBrother = parent->ptr[pIndex-1];//左相邻兄弟
						}
						if (pIndex < parent->keyNum){
							rightBrother = parent->ptr[pIndex+1];//右兄弟
						}
					}
					else if (parent->keyNum > M-1){//合并后的关键字超过 M-1,需要分裂
						BTree newNode = NULL,r = NULL;
						bool finished = false;
						p = parent;
						keyType k = 0;
						while (p != NULL){
							int mid = (int)ceil(M/2.0);
							split(p,mid,&newNode);
							k = p->key[mid];//分裂需要插入 到父节点的关键字
							r = p;
							p = p->parentNode;
							if (p != NULL){
								index = search(p,k);//插入到父节点的位置.
								insert(p,index,k,newNode);
								if (p->keyNum < M){//M阶 的 树,最多 有 M-1个 关键字{
									finished = true;
									break;
								}
							}
						}
						if (finished == false){//空树,或者 根节点的 关键字 大于 M-1
							newRoot(t,k,r,newNode);
						}
						return true;
					}
				}
				//根节点最少一个,当为0时,将合并的节点设置为新的 根节点。
				if (p->keyNum == 0){
					free(p);
					*t = merger;
					return true;
				}
			}
			else{//error
				printf("------------ERROR--------------\n");
			}
		}
	}
	return false;//没找到
}

//层序遍历
void levelOrderTraverse(BTree tree){
	if (tree != NULL){
		LinkQueue queue;
		queueInit(&queue);
		enqueue(&queue,tree);
		int count = 1;
		int nextCount = 0;
		int level = 1;
		printf("-----------层序遍历--------------\n");
		while (!queueEmpty(queue)){
			printf("第%d层数据:",level);
			for (int i = 0; i < count; i++){
				BTree front;
				dequeue(&queue,&front);
				for (int j= 0; j <= front->keyNum; j++){
					if (j >=1){
						printf("%d\t",front->key[j]);
					}
					if (front->ptr[j] != NULL){
						enqueue(&queue,front->ptr[j]);
					}
					nextCount++;
				}
				printf("------");
			}
			printf("\n");
			count = nextCount;
			nextCount = 0;
			level ++;
		}
		queueDestory(&queue);
		printf("\n");
	}
}


int _tmain(int argc, _TCHAR* argv[])
{
	BTree tree;
	initTree(&tree);
	int array [11] = {22,11,55,66,77,33,44,99,88,22,33};
	for (int i = 0; i < 11; i++){
		insertBTree(&tree,array[i]);
		//printf("-----插入%d后------------\n",array[i]);
		//levelOrderTraverse(tree);
	}
	levelOrderTraverse(tree);
	return 0;
}

其中包含了部分不正确的 B-树 删除代码,在处理 下面 这个问题时,不正确。书中 也没提到 这种情况。希望 以后 完善。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是可以回答的Python代码: ```python import requests from bs4 import BeautifulSoup import csv url = "https://bj.58.com/ershoufang/" params = { "PGTID": "0d100000-0000-18d3-3a3b-6c133d6d2fbc", "ClickID": 1 } res = requests.get(url, params=params) res.encoding = "utf-8" soup = BeautifulSoup(res.text, "html.parser") with open("house_info.csv", "w", newline="", encoding="utf-8") as csvfile: writer = csv.writer(csvfile) writer.writerow(["标题", "面积", "均价", "总价", "标签", "用途", "户型", "装修情况", "朝向"]) for house in soup.select(".list > li"): title = house.select_one(".title > a").string.strip() area = house.select_one(".area > div > div:first-child").string.strip() unit_price = house.select_one(".unit > span:first-child").string.strip() total_price = house.select_one(".total > span").string.strip() tags = "/".join([tag.string.strip() for tag in house.select(".baseinfo > div:first-child > a")]) usage = house.select_one(".baseinfo > div:first-child > span").string.strip() layout = house.select_one(".baseinfo > div:nth-child(2) > span:nth-child(1)").string.strip() decoration = house.select_one(".baseinfo > div:nth-child(2) > span:nth-child(2)").string.strip() orientation = house.select_one(".baseinfo > div:nth-child(2) > span:nth-child(3)").string.strip() writer.writerow([title, area, unit_price, total_price, tags, usage, layout, decoration, orientation]) print("数据已存储为 house_info.csv 文件。") ``` 注意:此代码的运行结果仅供参考,使用前需仿照运行环境和数据结构进行调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值