二叉树,顾名思义,就是子节点最多只有两个的树。由于最多只有两个,可以区分左右,我们称子节点为左子女和右子女,次序不可颠倒。根据二叉树的结构,我们可以得到以下特性: 1. 第i层的节点个数最多为2^(i-1)(i>=1)个; 2. 深度为h的二叉树最多有2^h -1(h>=1)个节点,最少为h个; 3. 若叶节点的个数为n1,度数为2的节点个数为n2,则n1=n2+1;
当二叉树满足一些特殊的条件时,会形成特殊的结构,可以很好的作为其他数据结构的底层结构,目前有如下几种特殊情况: 1. 完全二叉树。若二叉树的高度为h,则除了第h层的其他层都达到该层节点的最大个数,同时第h层的节点是从左至右连续排列的。由于节点顺序是紧密排列的,可以使用序列容器进行存储,主要应用于二叉堆。以下是完全二叉树的特性,设定根节点的索引位置为1: * 对于索引位置为i的节点,其到根节点的节点总数也为i; * 对于索引位置为i的节点,其左子女的索引位置为2*i,右子女的索引位置为2*i+1; * 对于索引位置为i(i>1)的节点,其父节点的索引位置为int(i/2); * 对于索引位置为i的节点,若节点总数为n且i>int(n/2),则该节点为叶子节点; * 对于索引位置为i的节点,若节点总数为n且int((n-1)/2)>i,则该节点必有两个子节点; * 对于任意节点,若其右子树的深度为d,则左子树的深度为d或d+1; * 若子节点总数为n,则叶子节点个数为l= math.ceil(n/2)。 2. 满二叉树。二叉树的每一层的节点个数都达到最大个数,因而满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树。 3. 平衡二叉树。对于树中的任意节点,其左右子树的高度相差不超过1。平衡二叉树要保证高度平衡,避免出现链表式的树。
在树中查找节点,就是采用递归方法,从根节点开始,一次判读根节点、左子树、右子树,从而找的对应的节点,一下是实现代码:
bool BTree::contains(int key, BTreeNode* &node) { if (node == NULL) { return false; } if (key == node->data) { return true; } return contains(key,node->leftChild) || contains(key,node->rightChild); }二叉树在插入时是没有固定规则,可自行定义,下面我使用构造满二叉树的方式进行插入,这里通过queue找到节点的插入位置,代码如下:
void BTree::insert(int key, BTreeNode* &node) { if (node == NULL) { node = new BTreeNode(key); } else { //按照插入顺序放置 queue<BTreeNode*> q; q.push(node); while(!q.empty()) { BTreeNode* temp = q.front(); q.pop(); if (temp->leftChild == NULL) { temp->leftChild = new BTreeNode(key); return; } if (temp->rightChild == NULL) { temp->rightChild = new BTreeNode(key); return; } q.push(temp->leftChild); q.push(temp->rightChild); } } }二叉树删除节点也是没有规则的,但不管使用什么规则,都要考虑下面三种情况: 1. 删除节点没有子节点。这种情况最简单,将该节点的父节点指向该节点的指针置为空,然后删除该节点即可; 2. 删除节点有一个子节点。此时将该节点的父节点指向该节点的指针置为指向改节点的子节点,然后删除该节点; 3. 删除节点有两个子节点。这里采用的策略是先查找到该节点的右子树中的最后一个节点,然后将查找到的节点的键值与该节点的键值进行交换,最后使用递归方法删除查找到的节点。 下面是二叉树删除的实现代码:
void BTree::remove(int key, BTreeNode* &node) { if (node == NULL) { return; } if (key == node->data) { if (node->leftChild != NULL && node->rightChild != NULL) { //查找子树的最后一个节点 queue<BTreeNode*> q; q.push(node->leftChild); q.push(node->rightChild); while(true) { BTreeNode* curNode = q.front(); q.pop(); if (curNode->leftChild != NULL) { q.push(curNode->leftChild); } if (curNode->rightChild != NULL) { q.push(curNode->rightChild); } if (q.empty()) { node->data = curNode->data; curNode->data = key; remove(key,node); return; } } } else { BTreeNode* temp = node; node = (node->leftChild != NULL)?node->leftChild:node->rightChild; delete temp; return; } } remove(key,node->leftChild); remove(key,node->rightChild); }若要清理整棵树的节点,可以使用递归的方法清理节点的左右子树,然后释放节点自己,下面是实现代码:
void BTree::clear(BTreeNode* &node) { if (node == NULL) { return; } clear(node->leftChild); clear(node->rightChild); delete node; node = NULL; }下面,我们对算法进行测试,结果如下:
BTree* tree = new BTree(); for (int i = 0; i < 10; ++i) { tree->insert(i); } cout<<"contains 5:"<<tree->contains(5)<<endl; //contains 5:1 cout<<"contains 10:"<<tree->contains(10)<<endl; //contains 10:0 BTreeNode* minNode = tree->findMin(); if (minNode != NULL) { cout<<"findMin:"<<minNode->data<<endl; //findMin:0 } BTreeNode* maxNode = tree->findMax(); if (minNode != NULL) { cout<<"findMax:"<<maxNode->data<<endl; //findMax:9 } tree->breadthFirstTravel(); //bft:0 1 2 3 4 5 6 7 8 9 tree->depthFirstTravel_DLR(); //depthFirstTravel_DLR:0 1 3 7 8 4 9 2 5 6 tree->depthFirstTravel_LDR(); //depthFirstTravel_LDR:7 3 8 1 9 4 0 5 2 6 tree->depthFirstTravel_LRD(); //depthFirstTravel_LRD:7 8 3 9 4 1 5 6 2 0 tree->remove(1); tree->breadthFirstTravel(); //bft:0 9 2 3 4 5 6 7 8 tree->depthFirstTravel_DLR(); //depthFirstTravel_DLR:0 9 3 7 8 4 2 5 6 tree->depthFirstTravel_LDR(); //depthFirstTravel_LDR:7 3 8 9 4 0 5 2 6 tree->depthFirstTravel_LRD(); //depthFirstTravel_LRD:7 8 3 4 9 5 6 2 0 tree->clear(); tree->breadthFirstTravel(); //bft:二叉树的查找操作需要遍历整棵树,其算法复杂度为O(n);插入操作根据插入策略的不同而不同的,平均查找到插入位置的算法复杂度为O(logn);删除操作由于要先查找到节点,其算法复杂度以查找操作一致。