搜索树

二叉搜索树的应用不是很多,但他是重要的数据结构AVL树红黑树的基础。

二叉搜索树

二叉搜索树是一棵二叉树,可能为空;一棵非空的二叉搜索树满足以下特征:

  1. 每个元素有一个关键字,并且任意两个关键字都不同;因此,所有的关键字都是唯一的;
  2. 在根节点的左子树中,元素的关键字(如果有的话)都小于根节点的关键字;
  3. 在根节点的右子树中,元素的关键字(如果有的话)都大于根节点的关键字;
  4. 根节点的左、右子树也都是二叉搜索树。

若是有重复值的二叉搜索树则把大于改成大于等于,小于改成小于等于。

索引二叉搜索树源于普通二叉搜索树,只是在每个节点总添加了leftSize域,这个域的值是该节点左子树的元素个数
在这里插入图片描述

二叉搜索树的操作和实现

二叉搜索树的操作有:搜索、插入、删除。
搜索是从根开始查找,如果根为空,那么搜索树不包含任何元素,查找失败;如果不空,则将theKey与根的关键字相比较。如果theKey小,那么就不必在右子树中查找,只要查找左子树。

template<class K, class E>
pair<const K, E>* binarySearchTree<K,E>::find(const K& theKey) const
{//返回值是匹配数对的指针
 // 如果没有匹配的数对,返回值为NULL
   // p从根节点开始搜索,寻找关键字等于theKey的一个元素
   binaryTreeNode<pair<const K, E> > *p = root;
   while (p != NULL)
      // 检查元素p->element
      if (theKey < p->element.first)
         p = p->leftChild;
      else
         if (theKey > p->element.first)
            p = p->rightChild;
         else // 找到匹配的元素
            return &p->element;

   // 没有匹配的数对
   return NULL;
}

插入要先通过查找来确定是否存在某个元素,其关键字与thePair.first相同,如果搜索成功,那么就用thePair.second替代该元素的值。如果搜索不成功,那么就将新元素作为搜索中断节点的孩子插入二叉搜索树。

template<class K, class E>
void binarySearchTree<K,E>::insert(const pair<const K, E>& thePair)
{// 插入thePair。如果存在与其关键字相同的数对,则覆盖
   // 寻找插入位置
   binaryTreeNode<pair<const K, E> > *p = root,
                                     *pp = NULL;
   while (p != NULL)
   {// 检查元素p->element
      pp = p;
      // p移到它的一个孩子节点
      if (thePair.first < p->element.first)
         p = p->leftChild;
      else
         if (thePair.first > p->element.first)
            p = p->rightChild;
         else
         {// 覆盖旧的值
            p->element.second = thePair.second;
            return;
         }
   }

   // 为thePair建立一个节点,然后与pp链接
   binaryTreeNode<pair<const K, E> > *newNode
                 = new binaryTreeNode<pair<const K, E> > (thePair);
   if (root != NULL) // 树不空
      if (thePair.first < pp->element.first)
         pp->leftChild = newNode;
      else
         pp->rightChild = newNode;
   else
      root = newNode; // 插入空树
   treeSize++;
}

删除,假设要删除的节点是p,需要考虑三种情况:1)p是树叶;2)p只有一棵非空子树;3)p有两棵非空子树。

template<class K, class E>
void binarySearchTree<K,E>::erase(const K& theKey)
{// 删除关键字等于theKey的数对

   // 查找关键字为theKey的节点
   binaryTreeNode<pair<const K, E> > *p = root,
                                     *pp = NULL;
   while (p != NULL && p->element.first != theKey)
   {// p移到它的一个孩子节点
      pp = p;
      if (theKey < p->element.first)
         p = p->leftChild;
      else
         p = p->rightChild;
   }
   if (p == NULL)
      return; // 不存在与关键字theKey匹配的数对

   // 重新组织树结构
   // 当p有两个孩子时处理
   if (p->leftChild != NULL && p->rightChild != NULL)
   {// 两个孩子
      // 转化成空或只有一个孩子
      // 在p的左子树中寻找最大元素
      binaryTreeNode<pair<const K, E> > *s = p->leftChild,
                                       *ps = p;  // parent of s
      while (s->rightChild != NULL)
      {// 移到最大的元素
         ps = s;
         s = s->rightChild;
      }

      // 将最大元素s移到p,但不是简单的移动
      // p->element = s->element,因为key是常量
      binaryTreeNode<pair<const K, E> > *q =
         new binaryTreeNode<pair<const K, E> >
             (s->element, p->leftChild, p->rightChild);
      if (pp == NULL)
         root = q;
      else if (p == pp->leftChild)
              pp->leftChild = q;
           else
              pp->rightChild = q;
      if (ps == p) pp = q;
      else pp = ps;
      delete p;
      p = s;
   }

   // p最多有一个孩子
   // 把孩子指针存放在c
   binaryTreeNode<pair<const K, E> > *c;
   if (p->leftChild != NULL)
      c = p->leftChild;
   else
      c = p->rightChild;

   // 删除p
   if (p == root)
      root = c;
   else
   {// p是pp的左孩子还是右孩子?
      if (p == pp->leftChild)
         pp->leftChild = c;
      else pp->rightChild = c;
   }
   treeSize--;
   delete p;
}

应用

交叉分布

通道布线与交叉:在交叉分布问题中,从一个布线通道开始,在通道的顶部和底部各有n个针脚。布线区域是带阴影的长方形区域。针脚从左到右从1到n编号,另外,对[1,2,3,…,n]的一个排列C,用一条线路将顶部的针脚i与底部的针脚j连接起来,如图的实例中:C=[8,7,4,2,5,1,9,3,10,6]。实现这些连接的n条线路从1到n编号,线路i连接顶部的针脚i和底部的针脚j,当且仅当i<j时,线路i在线路j的左边
在这里插入图片描述
每一个交叉用一个数对(i,j)表示,其中i和j是两条交叉的线路。为了避免一个交叉提及两次,要求i<j(注意,交叉(10,9)(9,10)是一样的)。线路i和j交叉(i<j)当且仅当 C i > C j C_i>C_j Ci>Cj。令 k i k_i ki表示这种数对(i,j)的数量。
在这里插入图片描述
分布交叉:为了使通道的上半部和下半部的布线平衡,我们要求每一部分含有数量大致相同的交叉(上半部应该有 ⌊ k / 2 ⌋ \lfloor k/2\rfloor k/2下半部应有 ⌊ k / 2 ⌋ \lfloor k/2\rfloor k/2)

void main(void)
{
	//定义要解决的问题实例
	//在通道底部的连接点theC[1:10]
	int theC[] = {0, 8, 7, 4, 2, 5, 1, 9, 3, 10, 6};
	//交叉数量,k[1:10]
	int k[] = {0, 7, 6, 3, 1, 2, 0, 2, 0, 1, 0};
	int n = 10;			//在通道每一遍的针脚数量
	int theK = 22;		//交叉总数量

	//生成数据结构
	arrayList<int> theList(n);
	int *theA = new int[n + 1],		//顶端的排列
		*theB = new int[n + 1],		//底端的排列
		*theX = new int[n + 1];		//中间的排列
	int crossingsNeeded = theK / 2;	//需要在上半部分保留的交叉数
	// 从右到左扫描线路
	int currentWire = n;
	while(crossingsNeeded > 0)
	{//在上半部需要更多的交叉
		if (k[currentWire] < crossingsNeeded)
		{//使用来自currentWire的所有交叉
			theList.insert(k[currentWire],currentWire);
			crossingsNeeded -= k[currentWire];
		}
		else
		{//使用来自currentWire的crossingsNeeded
			theList.insert(crossingsNeeded, currentWire);
			crossingsNeeded = 0;
		}
		currentWire--;
	}
	//确定中间的线路排列
	//第一个线路currentWire次数相同
	for(int i = 1;i <= currentWire; i++)
		theX[i] = i;
	//剩余线路的次序来自表
	for (int i = currentWire + 1; i <= n; i++)
		theX[i] = theList.get(i - currentWire - 1);
	//计算上半部的排列
	for(int i = 1;i <= n;i++)
		theA[theX[i]] = i;
	//计算下半部的排列
	for(int i = 1;i <= n;i++)
	 	theB[i] = theC[theX[i]];
	 cout << "A is ";
	 for(int i = 1;i <= n;i++)
	 	cout << theA[i] << " ";
	 cout << endl;
	 cout << "B is ";
	 for(int i = 1;i <= n;i++)
	 	cout << theB[i] <<" ";
	 cout << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值