这是第一篇文章也是第一个题目,希望我能坚持下去,突破自己的编程功底。
1 题目:把二元查找树转变成排序的双向链表(树)
输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。
10
/ /
6 14
/ / / /
4 8 12 16
转换成双向链表
4=6=8=10=12=14=16。
首先我们定义的二元查找树 节点的数据结构如下:
struct BSTreeNode
{
int m_nValue; // value of node
BSTreeNode *m_pLeft; // left child of node
BSTreeNode *m_pRight; // right child of node
};
2 详解:
2.1 知识点复习:二叉查找树是什么?
2.1.1 树
树(Tree)是n(n≥0)个结点的有限集。在任意一棵非空树中:(1)有且仅有一个特定的被称为根(Root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶子(Leaf)或终端结点。度不为0的结点称为非终端结点或分支结点。
树的度是树内各结点的度的最大值。
结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲(Parent)。
结点的层次(Level)是从根结点开始计算起,根为第一层,根的孩子为第二层,依次类推。树中结点的最大层次称为树的深度(Depth)或高度。
如果将树中结点的各子树看成从左至右是有次序的(即不能互换),则称该树为有序树,否则称为无序树。
2.1.2 二叉树
二叉树(Binary Tree)的特点是每个结点至多具有两棵子树(即在二叉树中不存在度大于2的结点),并且子树之间有左右之分。
二叉树的性质:
(1)、在二叉树的第i层上至多有2i-1个结点(i≥1)。
(2)、深度为k的二叉树至多有2k-1个结点(k≥1)。
(3)、对任何一棵二叉树,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
一棵深度为k且有2k-1个结点的二叉树称为满二叉树。
可以对满二叉树的结点进行连续编号,约定编号从根结点起,自上而下,自左至右,则由此可引出完全二叉树的定义。深度为k且有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应时,称之为完全二叉树。
(4)、具有n个结点的完全二叉树的深度为不大于log2n的最大整数加1。
(5)、如果对一棵有n个结点的完全二叉树的结点按层序编号(从第1层到最后一层,每层从左到右),则对任一结点i(1≤i≤n),有
a、如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点x(其中x是不大于i/2的最大整数)。
b、如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
c、如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
二叉树的链式存储:
链式二叉树中的每个结点至少需要包含三个域,数据域和左、右指针域。
二叉树的遍历:
假如以L、D、R分别表示遍历左子树、访问根结点和遍历右子树,则可有DLR、DRL、LRD、LDR、RLD、RDL这六种遍历二叉树的方案。若限定先左后右,则只有三种方案,分别称之为先(根)序遍历、中(根)序遍历和后(根)序遍历,它们以访问根结点的次序来区分。
2.1.3 二叉查找树
二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:
(1)、若它的左子树不为空,则左子树上所有结点的值均小于它的根结点的值;
(2)、若它的右子树不为空,则右子树上所有结点的值均大于它的根结点的值;
(3)、它的左、右子树也分别为二叉查找树。
还有一个性质是二叉查找树一定没有重复的元素
2.1.4 二叉查找树的操作
一般操作就是插入与查找,无非就是根据关键字的大小直接找到底层为NULL的节点处或者是重复了那么直接返回NULL(插入)或者返回(查找)。其中有意思的是二叉查找树的删除节点操作,这里贴出代码分析下:
static bst_p find_node_by_data(bst_p root,data_type data)
{
bst_p p = root;
int flag = 0;
while(p!=NULL&&flag==0) {
if(p->data == data)
flag = 1; // 找到
else {
p = (data < p->data) ? p->lchild : p->rchild;
}
}
/*if(p==NULL)
return NULL;
else */
return p;
}
void delete_bst_node(bst_p *root,data_type data)
{
bst_p s = NULL;
bst_p p = NULL; // p是要指向要删除的节点的指针,data是要删除的节点的值
bst_p q = NULL; // q是用来暂存的
//1 找到要删除的节点
p = find_node_by_data(*root,data);
if(p == NULL) {
perror("find node by data: not found\n");
return;
}
//2 运用二叉查找树的删除方法
if(!p->lchild) { // 不用判断同时为空的情况,那种可以包含在单子树为空的里面
q = p;
p = p->rchild;
free(q);
} else if(!p->rchild) {
q = p;
p = p->lchild;
free(q);
} else {
q = p;
s = p->lchild;
while(s->rchild) {
q = s; // q保存s的双亲
s = s->rchild; // s 下移
}
p->data = s->data;
if(p!=q)
q->rchild = s->lchild;
else
q->lchild = s->lchild;
free(s);
}
//注意因为这种删除的方法并没有改变原来那个要删除的节点在整个二叉树的结构,所以不用怕会动root
}
删除的原理我总结了是根据保持这颗排序二叉树在删除节点之前和之后都要保持中序遍历序列的不变就行了。而在中序遍历访问的时候任何一个节点他的前驱一定是它左子树的最右的叶子节点或者是最右的度为1的节点。那么,可以把这个最右节点的值直接拷贝到要删除的节点,同时处理下这个最右节点的左子树,最后释放这个最右节点就完了。这就是void delete_bst_node(bst_p *root,data_type data)函数的else里面的代码的思想。参考文献:blog.youkuaiyun.com/npy_lp/article/details/7426431