二叉树除过使用链式存储外还可以使用线性存储,这里我们使用数组模拟这一过程。
如下所示为一颗二叉树,其中根节点为 0 11,其中0是我们人为加上的序号,通过序号之间的关系把二叉树存放到数组中。
对于以上二叉树,我们使用数组进行存储:int arr[] = { 11,45,63,22,57,81,55 };
0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
11 | 45 | 63 | 22 | 57 | 81 | 55 |
他们之间的关系如下:
- 根节点与左右孩子的关系,令根节点的下标为 i
- 左孩子:下标= 2i+1
例如:根结点 i=0,左孩子=array[1]=45 - 右孩子:下标= 2i+2
例如:根结点 i=0,右孩子=array[2]=45
- 左孩子:下标= 2i+1
- 孩子结点与根节点的关系,令左孩子下标为 j,右孩子为k。(其中k=j+1)
- 左->根节点:下标 = (j-1)/2
- 右->根节点:下标 = (k-1)/2
- i 下标所在的层次为 l o g 2 i + 1 log_2^{i+1} log2i+1 。
递归——先序中序后序遍历
typedef int ElemType;
#define END -1 // 输入时以-1结束
// 先序遍历
void PreOder(ElemType* parr, int i, int n)
{
if (i < n && parr[i] != END)
{
std::cout << parr[i] << " ";
PreOder(parr, 2 * i + 1, n);
PreOder(parr, 2 * i + 2, n);
}
}
void PreOder_Ar(ElemType* parr, int n)
{
if (NULL == parr) return;
PreOder(parr, 0, n);
std::cout << std::endl;
}
// 中序遍历
void InOder(ElemType* parr, int i,int n)
{
if (i < n && parr[i] != END)
{
InOder(parr, 2 * i + 1, n);
std::cout << parr[i] << " ";
InOder(parr, 2 * i + 2, n);
}
}
void InOder_Ar(ElemType* parr, int n)
{
if (NULL == parr) return;
InOder(parr, 0, n);
std::cout << std::endl;
}
// 后序遍历
void PastOder(ElemType* parr, int i, int n)
{
if (i < n && parr[i] != END)
{
PastOder(parr, 2 * i + 1, n);
PastOder(parr, 2 * i + 2, n);
std::cout << parr[i] << " ";
}
}
void PastOder_Ar(ElemType* parr, int n)
{
if (NULL == parr) return;
PastOder(parr, 0, n);
std::cout << std::endl;
}
int main()
{
/*
31
23 12
66 5 17
70 62 88 55
*/
int arr[] = { 31,23,12,66,-1,5,17,70,62,-1,-1,-1,88,-1,55 };
int n = sizeof(arr) / sizeof(arr[0]);
PreOder_Ar(arr, n);
InOder_Ar(arr,n);
PastOder_Ar(arr, n);
std::cout << std::endl;
return 0;
}
非递归——先序中序后序
void NicePreOrder(ElemType* parr, int n)
{
if (NULL == parr || n < 1) return;
std::stack<int> st;
int i = 0; // 根结点下标
while (!st.empty() || i < n)
{
while (i < n && parr[i] != END) // 向左入栈
{
st.push(i);
std::cout << parr[i] << " ";
i = 2 * i + 1;
}
i = st.top(); st.pop();
i = 2 * i + 2;
}
std::cout << std::endl;
}
void NiceInOrder(ElemType* parr, int n)
{
if (NULL == parr || n < 1) return;
std::stack<int> st;
int i = 0; // 根结点下标
while (!st.empty() || i < n)
{
while (i < n && parr[i] != END) // 向左入栈
{
st.push(i);
i = 2 * i + 1;
}
i = st.top(); st.pop();
std::cout << parr[i] << " ";
i = 2 * i + 2;
}
std::cout << std::endl;
}
void NicePastOrder(ElemType* parr, int n)
{
if (NULL == parr || n < 1) return;
std::stack<int> st; // 存放下标
int i = 0, flg = 0; // flg 用于标记访问过的子树
while (!st.empty() || i < n)
{
while (i < n && parr[i] != END)
{
st.push(i);
i = 2 * i + 1;
}
i = st.top(); st.pop();
int r = 2 * i + 2; /* 右孩子下标 */
// 判断右子树是否遍历过(空or遍历过)
if (r >= n || parr[r] == END || r == flg)
{
std::cout << parr[i] << " ";
flg = i;
i = n; /* 左右子树已经遍历完,出栈 */
}
else
{
st.push(i); // 继续入栈
i = r;
}
}
std::cout << std::endl;
}
层次遍历
顺序每层从左至右遍历。
void LeveOrder(ElemType* parr, int n)
{
if (NULL == parr || n < 1) return;
std::queue<int> que;
que.push(0);
int i = 0;
while (!que.empty() && i < n)
{
i = que.front(); que.pop();
std::cout << parr[i] << " ";
if (2 * i + 1 < n && parr[2*i + 1] != END)
{
que.push(2 * i + 1);
}
if (2 * i + 2 < n && parr[2*i + 2] != END)
{
que.push(2 * i + 2);
}
}
std::cout << std::endl;
}
左右交替层次遍历。
void LeveOrder_Z(ElemType* parr, int n)
{
if (NULL == parr || n < 1) return;
std::stack<int> st1;
std::stack<int> st2;
st1.push(0);
int i = 0;
while (!st1.empty() || !st2.empty())
{
while (!st1.empty()) // 从左向右入栈
{
i = st1.top(); st1.pop();
std::cout << parr[i] << " ";
if (2 * i + 1 < n && parr[2 * i + 1] != END)
{
st2.push(2 * i + 1);
}
if (2 * i + 2 < n && parr[2 * i + 2] != END)
{
st2.push(2 * i + 2);
}
}
while (!st2.empty()) // 从右向左入栈
{
i = st2.top(); st2.pop();
std::cout << parr[i] << " ";
if (2 * i + 2 < n && parr[2 * i + 2] != END)
{
st1.push(2 * i + 2);
}
if (2 * i + 1 < n && parr[2 * i + 1] != END)
{
st1.push(2 * i + 1);
}
}
}
std::cout << std::endl;
}
其实通过以上几段代码,我们就能发现,无论是链式的存储结构还是顺序的存储结构,他们都能表示一棵二叉树。并且在对二叉树的一些操作上的算法也是极其相似的。以上几段代码都是脱胎于之前博文中的链式二叉树的遍历算法。因此我们可以照猫画虎,按照以往分析的经验对这些算法进行实现 。