數據結構:樹的概念以及二叉樹的實現

 

目錄

一、樹的概念

二、二叉樹的概念

三、二叉樹的實現


一、樹的概念

  • 樹是一種非線性的數據結構

 

  • 度:一個節點包含子樹的數目(換種話來說,有多少條分支)
  • 節點:用於存放數據,是構成複雜樹的基本結構
  • 雙親節點:如:A爲B、C的雙親節點
  • 孩子節點:如:B、C爲A的孩子節點
  • 兄弟節點:如:B、C有同個雙親節點,則B、C互爲兄弟節點
  • 葉子節點:度爲0的節點(沒有分支的節點),如D、E、F節點

樹的層次與深度

  • 樹的層次:從根節點開始算起,根節點爲第一層,每經過對應的孩子節點,則層數加1
  • 樹的高度:樹中最高的層數爲樹的高度

二、二叉樹的概念

二叉樹的定義:

  • 任意節點的度小於或者等於2的樹(即:一個樹中,一個節點的分支最多有2個)
  • 樹中的節點有序排列,次序不能顛倒!

二叉樹的分類:

  • 滿二叉樹:除了葉子節點外,每一個節點的度都爲2(每一個節點都有2個孩子節點)
  • 完美二叉樹:除了葉子節點外,每一個節點的度都爲2,並且填充滿每一層(即最後一層一定全爲葉子節點)
  • 完全二叉樹:最簡單的識別方法:從根節點開始,自上而下,從左到右,能夠按順序進行連續編號
  • 三種二叉樹的包含關係:如下圖所示


 

 


 


 二叉樹的存儲方式:

  • 順序存儲:爲完全二叉樹時,可以完全填滿空間。但爲其他二叉樹時,則會浪費大量存儲空間。因此二叉樹的存儲方式一般均爲鏈式存儲。
  • 鏈式存儲:一般爲將存儲節點定義爲:左孩子指針、數據、右孩子指針

二叉樹的插入原理與刪除原理:

  • 插入:首先二叉樹大小順序爲,小、中、大。然後通過比較插入數據與根節點的大小,來插入根節點的對應數據。我們插入的方法就是通過遞歸的方式,不斷將比較後的節點作爲新的根節點來進行插入。
  • 刪除:刪除某個節點(根節點),可以使用該節點的左子樹最大的節點替換或者使用該節點的右子樹最小節點替換。我們需要做的與插入類似,通過遞歸的方法,不斷將節點進行刪除與替換。

 

二叉樹的幾種遍歷方式:(不同種順序的區別:根節點(父親節點)的位置不同)

  • 前序遍歷:根節點(雙親節點)->左子節點->右子節點
  • 中序遍歷:左子節點->根節點(雙親節點)->右子節點
  • 後序遍歷:左子節點->右子節點->根節點(雙親節點)
  • 按層遍歷:從上到下,從左到右,一層一層遍歷(通過隊列來實現

 

三、二叉樹的實現

  • 二叉樹、與隊列的結構體(用於實現二叉樹的按層遍歷)
//二叉樹節點
typedef struct tree_Node
{
	int data;
	struct tree_Node *lchild, *rchild;
}treeNode,*linktree;


//隊列存儲節點
typedef struct sq_Node
{
	struct tree_Node *data;
	struct sq_Node *next;
}sqNode,*sqNode_p;

//隊列管理結構體
typedef struct sq
{
	struct	sq_Node *front;
	struct	sq_Node *rear;
}sq,*sq_p;
  • 初始化隊列、入隊、出隊、判斷隊列是否爲空。
/*初始化隊列*/
sq_p init_sq()
{
	sq_p sq = malloc(sizeof(sq));
	if(sq != NULL)
	{
		//開闢 隊列鏈表 的頭結點,隊頭指向頭結點
		sq->front = sq->rear = (sqNode_p)malloc(sizeof(sqNode));
		sq->rear->next = NULL;
		return sq;
	}
}

/*
	判斷隊列是否爲空
 */
bool isEmpty(sq_p sq)
{
	return sq->front ==  sq->rear;
}

/*
	入隊列:
	隊列的地址   隊列存儲節點中的數據
*/
bool in_sq(sq_p sq, linktree data)
{
	sqNode_p new = malloc(sizeof(sqNode));//開闢新的隊列存儲節點
	if (new != NULL)
	{
		new->data = data;//優先處理新節點
		new->next = NULL;

		sq->rear->next = new;//隊尾的節點鏈接上新的節點
		sq->rear = new;//新的節點爲new

		return true;
	}
	else return false;
}

/*
	出隊列:
	隊列的地址 指向所需要數據的指針
*/
bool out_sq(sq_p sq, linktree *data)
{
	if(isEmpty(sq))
		return false;

	//隊列的front一直指向頭節點
	sqNode_p p = sq->front->next;//p指向第一個節點(相當於隊列中的隊頭)
	*data = p->data;
	sq->front->next = p->next;//將第一個節點從鏈表中刪除
	if (sq->rear == p)//如果爲只有一個節點的情況,則需要更新隊尾指針
	{
		sq->rear = sq->front;
	}
	free(p);

	return true;
}
  • 二叉樹節點的創建、插入、刪除
/*創建新節點*/
linktree newTreeNode(int n)
{
	linktree new = malloc(sizeof(treeNode));
	if(new != NULL)
	{
		new->data = n;
		new->lchild = NULL;
		new->rchild = NULL;
	}

	return new;
}

/*
	插入二叉樹節點:
	遞歸調用
*/
linktree insert_TreeNode(linktree new, linktree root)
{
	//插入檢驗
	if (new == NULL)//如果new爲NULL則直接返回
		return root;

	//退出條件(新節點插入位置)
	if (root == NULL)//如果root爲空,則新的根節點爲new
		return new;
	
	//比根節點數據大,則再比較右子節點(遞歸方式:以右子節點爲根節點,再與要插入的節點進行比較)
	if (new->data > root->data)
	{
		root->rchild = insert_TreeNode(new, root->rchild);
	}
	else if(new->data < root->data)
	{
		root->lchild = insert_TreeNode(new, root->lchild);
	}
	else
	{
		printf("%d is already exist\n", new->data);
	}

	return root;
}

/*
	插入二叉樹節點:
	非遞歸調用
 */
linktree insert_TreeNode_normal(linktree new, linktree root)
{
	//插入檢驗
	if(new == NULL)
		return root;

	//root爲空的情況
	if (root == NULL)
		return new;

	//root不爲空的情況
	linktree parents = NULL;
	linktree pos = root;
	while(pos)//parents保存要插入的節點位置
	{
		parents = pos;
		if(new->data > pos->data)
			pos = pos->rchild;
		else if(new->data < pos->data)
			pos = pos->lchild;
		else
			printf("%d is already exist\n", new->data);
	}

	//插入
	if (new->data > parents->data)
	{
		parents->rchild = new;
	}
	else if (new->data < parents->data)
	{
		parents->lchild = new;
	}

	return root;
}

/*
	刪除節點:
	刪除某個節點後,要用該節點的左子樹中最大的節點替換,或者
	使用右子樹中最小的節點替換
 */
linktree remove_TreeNode(linktree root , int n)
{
	//查找檢驗
	if (root == NULL)
		return NULL;

	//查找到需要刪除的節點
	if(n < root->data)//查找數據比根節點數據要小,往左邊查找
	{
		root->lchild = remove_TreeNode(root->lchild, n);
	}
	else if (n > root->data)//查找數據比根節點數據大,往右邊查找
	{
		root->rchild = remove_TreeNode(root->rchild, n);
	}
	else if(n == root->data)//找到刪除的節點,還需要判斷要刪除的節點是否有子樹
	{
		linktree tmp;
		/*
			兩種情況:
			1、有左子樹或者有右子樹(包括既有左子樹又有右子樹的情況)
			2、沒有左子樹,也沒有右子樹。(即:該節點爲葉子節點,直接刪除)
		 */
		if(root->lchild != NULL)
		{
			/*
				左子樹不爲空(只有左子樹、既有左子樹又有右子樹)的情況下,找到左子樹中最大的節點來代替根節點
			 */
			for(tmp=root->lchild; tmp->rchild!=NULL; tmp=tmp->rchild);
			root->data = tmp->data;//替換數據
			root->lchild = remove_TreeNode(root->lchild, tmp->data);//刪除左子樹中最大的節點。
		}
		else if (root->rchild != NULL)
		{
			/*
				左子樹爲空,有右子樹的清空
			 */
			for(tmp=root->rchild; tmp->lchild!=NULL; tmp=tmp->lchild);
			root->data = tmp->data;//替換數據
			root->rchild = remove_TreeNode(root->rchild, tmp->data);//刪除右子樹中最小的節點
		}
		else
		{
			/*
				爲葉子節點的情況
			 */
			free(root);
			return NULL;
		}

	}

	return root;
}
  • 二叉樹的排序(按層遍歷、前序、中序、後序)
/*
	按層遍歷:
 */
void level_travel(linktree root)
{
	if (root == NULL)
		return;

	//先創建一個隊列
	sq_p head = init_sq();
	//根結點入隊
	in_sq(head, root);

	linktree tmp;
	while(1)
	{
		if (!out_sq(head,&tmp))//出隊,如果爲空隊則跳出循環
			break;

		printf("%d\t", tmp->data);
		if (tmp->lchild != NULL)
		{
			in_sq(head,tmp->lchild);
		}
		if (tmp->rchild != NULL)
		{
			in_sq(head,tmp->rchild);
		}
	}

}

/*
	前序遍歷節點:
	父節點 - 左子節點 - 右子節點
*/
void pre_travel(linktree root)
{
	//退出條件
	if (root == NULL)
		return;

	printf("%d\t", root->data);
	pre_travel(root->lchild);
	pre_travel(root->rchild);
}

/*
	中序遍歷節點:
	左子節點 - 父節點 - 右子節點
*/
void mid_travel(linktree root)
{
	//退出條件
	if(root == NULL)
		return;

	mid_travel(root->lchild);//左子節點
	printf("%d\t", root->data);//父節點
	mid_travel(root->rchild);//右子節點
}

/*
	後序遍歷節點:
	左子節點 - 右子節點 - 父節點
 */
void last_travel(linktree root)
{
	//退出條件
	if(root == NULL)
		return;

	last_travel(root->lchild);//左子節點
	last_travel(root->rchild);//右子節點
	printf("%d\t", root->data);//父節點

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章