1. 程式人生 > >二叉樹演算法引發的指標引數傳遞和引用的思考

二叉樹演算法引發的指標引數傳遞和引用的思考

最近想熟悉一下基本的資料結構和演算法,於是寫了一個二叉樹程式,功能很簡單,只有二叉樹的建立和遍歷。在這個過程中,卻發現了一些平時沒有注意到的細節問題,寫出來作為總結和大家分享。

待討論和遇到的細節問題如下:

(1)常見的定式思維:指標作為引數傳遞,是否就不需要再賦值,因為指標指向的值會同步更改,但是,你有考慮過,如果指標變數本身的值被更改了呢?怎麼辦?

(2)比較不常用的用法,針對指標變數的引用,你用過嗎?

前提:

(1)筆者考慮到自己一直混淆使用C和C++,於是,打算這個簡單的演算法程式採用純C來寫,因此使用的是C編譯器。

(2)關於C和C++的一些區別,筆者打算另外寫一些系列文章來總結分析。

(3)另外,因為是練習寫的程式,暫時還沒有新增記憶體釋放的方法。

程式的頭部定義以及通用的方法:

複製程式碼
#include <stdio.h>
#include <malloc.h>

#define TRUE 1
#define FALSE -1

typedef char ElemType; 
typedef int BOOL;

typedef struct _BinaryTreeNode
{
ElemType elem;
struct _BinaryTreeNode* left;
struct _BinaryTreeNode* right;
}BinareTreeNode, 
*BiTree; void PrintNode(ElemType elem) { printf("%c ", elem); } void PreOrderTraverse(BinareTreeNode* pNode, void(* Visit)(ElemType elem)) { if (NULL != pNode) { Visit(pNode->elem); PreOrderTraverse(pNode->left, PrintNode); PreOrderTraverse(pNode->right, PrintNode); } }
複製程式碼

筆者最初寫的程式如下:

複製程式碼
void
PreOrderCreateBinaryTree(BinareTreeNode* pNode) { ElemType elem; scanf("%c", &elem); if ('#' == elem) { pNode = NULL; } else { pNode = (BinareTreeNode*)malloc(sizeof(BinareTreeNode)); pNode->elem = elem; PreOrderCreateBinaryTree(pNode->left); PreOrderCreateBinaryTree(pNode->right); } } int main() { BinareTreeNode* pHeadNode = NULL; PreOrderCreateBinaryTree(pHeadNode); PreOrderTraverse(pHeadNode, PrintNode); return 0; }
複製程式碼

你能看出來問題在哪裡嗎?先思考,不要急著往後看。

具體問題現象:

原本以為這樣寫是沒有問題的,實際上,main函式中的pHeadNode的值一直都是NULL,導致PreOrderTraverse時沒有任何值輸出。
輸入:ABD##E##C## 遍歷後輸出:無,原因是傳入的引數值pHeadNode為NULL。

具體問題分析:

為什麼會這樣呢?

本文討論的第一個問題浮出水面:常見的定式思維:指標作為引數傳遞,是否就不需要再賦值?

分析:

雖然是指標傳遞,main函式中的pHeadNode,是一個指標變數,指標變數的值為空,不存在指向的值。

PreOrderCreateBinaryTree函式中的形參pNode卻是另一個指標變數,剛開始的時候其值由main函式中傳入,也是NULL,後來pNode經過malloc被重新賦值。

問題是,main函式中的pHeadNode有沒有被同步改變呢?不是說指標傳遞,值會一起改變嗎?

其實,這裡犯了一個低階錯誤,就是說使用指標作為函式的引數,指標沒有被重新賦值的情況下,指標指向的值一定是會被同步更改的,但是如果指標作為一個變數,本身的值發生更改,那麼引數源是不會發生改變的。

舉個例子:

複製程式碼
1)指標指向的值會被同步更改
void FuncChangeObj(int* pInt)
{
(*pInt)++;
}
(2)指標本身的變數值被更改,此後,此函式內的指標與源指標將指向不同的值,也不會發生同步更改
void FuncNoChangeObj(int* pInt)
{
pInt = (int*)malloc(sizeof(int));
*pInt = 10;
}
複製程式碼

上例二叉樹程式先序遍歷不成功,就是因為先序建立二叉樹的時候,並沒有將二叉樹的根節點返回,導致pHeadNode一直為NULL。

那麼,是不是說,上述程式其實建立二叉樹成功,只是沒有返回樹的根節點呢?不然,因為也是同樣的原因,導致這個二叉樹並沒有建立成功,各個樹節點之間並無關聯,全是孤立的節點。

具體問題解決之道:

如何修改呢?
給出答案如下:
方法一:函式返回指標,對源指標進行賦值

複製程式碼
BinareTreeNode* PreOrderCreateBinaryTree(BinareTreeNode* pNode)
{
ElemType elem;
scanf("%c", &elem);
if ('#' == elem)
{
pNode = NULL;
}
else
{
pNode = (BinareTreeNode*)malloc(sizeof(BinareTreeNode));
pNode->elem = elem;
pNode->left = PreOrderCreateBinaryTree(pNode->left);
pNode->right = PreOrderCreateBinaryTree(pNode->right);
}
return pNode;
}

int main()
{
BinareTreeNode* pHeadNode = NULL;
pHeadNode = PreOrderCreateBinaryTree(pHeadNode);
PreOrderTraverse(pHeadNode, PrintNode);
return 0;
}
複製程式碼

方法二:使用指標的引用進行傳值,即對指標變數本身進行引用,而不是指標變數值傳遞
注:本方法即是對第二個問題的闡釋,但是要要注意,此方法在C語言編譯器中是無法編譯成功過的,因為C語言中並不存在引用的概念。

複製程式碼
void PreOrderCreateBiTree2(BiTree& T)
{
ElemType elem;
scanf("%c", &elem);
if ('#' == elem)
{
T = NULL;
}
else
{
T = (BinareTreeNode *)malloc(sizeof(BinareTreeNode));
T->elem = elem;
PreOrderCreateBiTree2(T->left);
PreOrderCreateBiTree2(T->right);
}
}

int main()
{
BiTree T = NULL;
PreOrderCreateBiTree2(T);
PreOrderTraverse(T, PrintNode);
return 0;
}
複製程式碼