转眼,学习编程已经快4个月了。虽然目前只掌握了C的一些基本语法,也只阅读了《数据结构与算法分析》的线性结构和树的部分,但起码我在自己拙劣的代码成功运行时能感受到快乐和成就感。我现在是大一,我想我会一直坚持学习下去。我希望在多年后,再次回首看自己写的博客时,能够高兴地看到自己的进步。
我决定写我人生中的这第一个博客有两个目的:1.加强对知识系统的理解和复习,毕竟这是一个很好的方式。通过发博客发代码的形式,我想我能更好地掌握那些我所迷惑的问题。2.我希望能提高我的表述能力,因为我觉得能够将自己对知识的理解很好的表述出来是很重要的,这也考验了我到底学的怎么样。
第一次的博客来写二叉树,是因为学习到这里的时候,我多多少少遇到了一些阻力。而这些阻力是学习前面那些线性结构时所没有遇到的。可能是因为这是我所接触到的第一个二维的结构吧,而且这里面的递归运用也很多。当然这不能称为困难的地方,因为目前为止我还没有掌握树的一些非递归操作。所以我准备在下一篇博客中尽我所能将树的一些非递归操作写出来。
1.二叉树的定义
<span style="font-family:Microsoft YaHei;"><span style="font-family:Courier New;font-size:12px;">typedef int ItemType
struct TreeNode{
ItemType Item;
struct TreeNode * Left;
struct TreeNode * Right;
};
typedef struct TreeNode * PtrToNode;
typedef PtrToNode SearchTree;
typedef PtrToNode Position;</span>
</span>
2.二叉树的创建、插入与删除
1)创建
<span style="font-family:Courier New;font-size:12px;">SearchTree MakeEmpty(SearchTree T)
{
return NULL;
}</span>
2)插入
<span style="font-family:Microsoft YaHei;"><span style="font-family:Courier New;font-size:12px;">SearchTree Insert(ItemType X, SearchTree T)
{
if (T == NULL)
{
T = (PtrToNode)malloc(sizeof(struct TreeNode));
if (T == NULL)
exit(1);
T->Item = X;
T->Left = T->Right = NULL;
}
else if (X > T->Item)//递归插入右子树
Insert(X, T->Right);
else if (X < T->Item)//递归插入左子树
Insert(X, T->Left);
return T; //若X已在树中,则直接返回原树
}</span>
</span>
3)删除制定项
值得一提的删除操作是删除左右子树都不为空的节点这种情况。此时的核心思想是:找到待删除节点右子树的最小值替换掉待删除节点,再递归删除此最小值节点。
但此种删除方法的缺点是,有一种使左子树越来越重,而右子树越来越轻的趋势。
<span style="font-family:Microsoft YaHei;"><span style="font-family:Courier New;font-size:12px;">SearchTree Delete(ItemType X, SearchTree T)
{
Position P;
if (T == NULL)
{
puts("Item not found");
exit(1);
}
else if (X > T->Item) //注意到函数的定义方式,只传递了一个指向节点的指针副本,
T->Right = (X, T->Right); //所以要将改变后的树节点指针传递给原来的节点。下同。
else if (X < T->Item)
T->Left = (X, T->Left);
else if (T->Left && T->Right) //已经找到节点,且这是节点左右子树都不为空的情况
{
P = FindMin(T);
T->Item = P->Item;
T->Right = Delete(P->Item, T); //递归删除右子树的最小节点
}
else //这里处理剩下的三种情况
{
P = T;
if (T->Left == NULL) //这里也处理了左右子树都为空的情况
T = T->Right;
else if (T->Right == NULL)
T = T->Left;
free(P);
}
return T;
}
//此方法需用到的查找树最小节点的函数
static SearchTree FindMin(SearchTree T)
{
if (T == NULL)
return T;
else if (T->Left == NULL)
return T;
else
return FindMin(T->Left);
}</span>
</span>
3.二叉树的遍历
利用递归可以很便利、简洁地实现遍历。具体过程也无需赘言,在此将前中后一并贴出。
<span style="font-family:Courier New;font-size:12px;">void PreOrderTraversal(SearchTree T, void(*pfun)(ItemType X))
{
if (T != NULL)
{
(*pfun)(T->Item);
PreOrderTraversal(T->Left, pfun);
PreOrderTraversal(T->Right, pfun);
}
}
void InOrderTraversal(SearchTree T, void(*pfun)(ItemType X))
{
if (T != NULL)
{
InOrderTraversal(T->Left, pfun);
(*pfun)(T->Item);
InOrderTraversal(T->Right, pfun);
}
}
void PostOrderTraversal(SearchTree T, void(*pfun)(ItemType X))
{
if (T != NULL)
{
PostOrderTraversal(T->Left, pfun);
PostOrderTraversal(T->Right, pfun);
(*pfun)(T->Item);
}
}</span>
想了想,还是把AVL树的插入操作也贴上吧。后面的博客会贴上非递归版本。
4.AVL树的递归插入
核心思想是:当递归插入到适当的位置时,沿插入路径挨个检查父辈节点的左右子树高度差(这里利用了节点中事先储存好的“高度”信息,储存"高度“信息的操作也在插入操作中进行),如果等于2则说明需要旋转来平衡高度。接下来,判断是应该进行single rotation 还是double rotation ,最后执行封装入相应函数的旋转操作。具体实现见代码。
//树的定义
typedef int ItemType;
struct AvlNode{
ItemType Item;
struct AvlNode * Left;
struct AvlNode * Right;
int Height;
};
typedef struct AvlTree * PtrToNode;
typedef PtrToNode AvlTree;
typedef PtrToNode Position;
//插入操作
AvlTree Insert(ItemType X, AvlTree T)
{
if (T == NULL)
{
T = (PtrToNode)malloc(sizeof(struct AvlNode));
if (T == NULL)
exit(1);
T->Item = X;
T->Left = T->Right = NULL;
T->Height = 0;
}
else if (X > T->Item)
{
T->Right = Insert(X, T->Right);
if (Height(T->Right) - Height(T->Left) == 2)
{
if (X > T->Right->Item)//执行单旋
T = RightSingleRotation(T);
else //执行双旋
T = RightDoubleRotation(T);
}
}
else if (X < T->Item)
{
T->Left = Insert(X, T->Left);
if ((Height(T->Left) - Height(T->Right)) == 2)
{
if (X < T->Left->Item)//执行单旋
T = LeftSingleRotation(T);
else //执行双旋
T = LeftDoubleRotation(T);
}
}
//计算并储存高度
T->Height = (T->Left->Height > T->Right->Height) ?
(T->Left->Height + 1) :
(T->Right->Height + 1);
return T;
}
//单旋及双旋函数各贴一个,另一种对称旋转与其类似。
static AvlTree RightSingleRotation(AvlTree T)
{
Position X = T->Right->Left;
AvlTree NewT = T->Right;
NewT->Left = T;
T->Right = X;
//不要忘了更新高度
T->Height = (T->Left->Height > T->Right->Height) ?
(T->Left->Height + 1) :
(T->Right->Height + 1);
NewT->Height = (NewT->Left->Height > NewT->Right->Height) ?
(NewT->Left->Height + 1) :
(NewT->Right->Height + 1);
return NewT;
}
static AvlTree RightDoubleRotation(AvlTree T)
{
T->Right = LeftSingleRotation(T->Right);
return RightSingleRotation(T);
}
//Height函数
static Height(Position P)
{
if (P == NULL)
return -1;
else
return P->Height;
}
啊!花了两个晚上,每晚两个小时左右,终于写完这篇博客了。
最后在这里默默地吐槽,其实我每天想花更多的时间学习编程的,可无奈破学校破事水课都太多了,还有无意义的点名的晚自习。所以还是抓紧一切可用时间学吧。