日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

算法【一】树

發(fā)布時(shí)間:2024/10/5 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法【一】树 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

關(guān)于樹(shù)你需要了解的一些概念:

?這是一棵普通的樹(shù):

有節(jié)點(diǎn),節(jié)點(diǎn)中有值,節(jié)點(diǎn)有指向下一個(gè)節(jié)點(diǎn),節(jié)點(diǎn)還有度,節(jié)點(diǎn)還有層次(也叫高度或者深度)

?結(jié)點(diǎn)的度

結(jié)點(diǎn)擁有的子樹(shù)數(shù)目稱(chēng)為結(jié)點(diǎn)的
?

節(jié)點(diǎn)關(guān)系?

結(jié)點(diǎn)子樹(shù)的根結(jié)點(diǎn)為該結(jié)點(diǎn)的孩子結(jié)點(diǎn)。相應(yīng)該結(jié)點(diǎn)稱(chēng)為孩子結(jié)點(diǎn)的雙親結(jié)點(diǎn)
圖2.2中,A為B的雙親結(jié)點(diǎn),B為A的孩子結(jié)點(diǎn)。
同一個(gè)雙親結(jié)點(diǎn)的孩子結(jié)點(diǎn)之間互稱(chēng)兄弟結(jié)點(diǎn)
圖2.2中,結(jié)點(diǎn)B與結(jié)點(diǎn)C互為兄弟結(jié)點(diǎn)。

后面接的全是空的,我們叫它們?nèi)~子結(jié)點(diǎn)如:G、H、I

節(jié)點(diǎn)的層次:

從根開(kāi)始定義起,根為第一層,根的孩子為第二層,以此類(lèi)推。

樹(shù)中結(jié)點(diǎn)的最大層次數(shù)稱(chēng)為樹(shù)的深度或高度。圖2.1所示樹(shù)的深度為4。

??二叉樹(shù)

由二叉樹(shù)定義以及圖示分析得出二叉樹(shù)有以下特點(diǎn):
1)每個(gè)結(jié)點(diǎn)最多有兩顆子樹(shù),所以二叉樹(shù)中不存在度大于2的結(jié)點(diǎn)。
2)左子樹(shù)和右子樹(shù)是有順序的,次序不能任意顛倒。
3)即使樹(shù)中某結(jié)點(diǎn)只有一棵子樹(shù),也要區(qū)分它是左子樹(shù)還是右子樹(shù)。

簡(jiǎn)單來(lái)說(shuō)就是:最多只有兩個(gè)字樹(shù)的樹(shù)就叫二叉樹(shù)

在日常編程中,二叉樹(shù)有一些性質(zhì)我們可能可以用到,比如

1)在二叉樹(shù)的第i層上最多有2i-1次方 個(gè)節(jié)點(diǎn) 。(i>=1)
2)二叉樹(shù)中如果深度為k,那么最多有2k次方-1個(gè)節(jié)點(diǎn)。(k>=1)
3)n0=n2+1 n0表示度數(shù)為0的節(jié)點(diǎn)數(shù),n2表示度數(shù)為2的節(jié)點(diǎn)數(shù)。
4)在完全二叉樹(shù)中,具有n個(gè)節(jié)點(diǎn)的完全二叉樹(shù)的深度為[log2n]+1,其中[log2n]是向下取整。
5)若對(duì)含 n 個(gè)結(jié)點(diǎn)的完全二叉樹(shù)從上到下且從左至右進(jìn)行 1 至 n 的編號(hào),則對(duì)完全二叉樹(shù)中任意一個(gè)編號(hào)為 i 的結(jié)點(diǎn)有如下特性:

(1) 若 i=1,則該結(jié)點(diǎn)是二叉樹(shù)的根,無(wú)雙親, 否則,編號(hào)為 [i/2] 的結(jié)點(diǎn)為其雙親結(jié)點(diǎn);
(2) 若 2i>n,則該結(jié)點(diǎn)無(wú)左孩子, 否則,編號(hào)為 2i 的結(jié)點(diǎn)為其左孩子結(jié)點(diǎn);
(3) 若 2i+1>n,則該結(jié)點(diǎn)無(wú)右孩子結(jié)點(diǎn), 否則,編號(hào)為2i+1 的結(jié)點(diǎn)為其右孩子結(jié)點(diǎn)。

對(duì)于第五點(diǎn)我們來(lái)詳細(xì)講下并給出一些實(shí)例,因?yàn)檫@個(gè)筆試題中經(jīng)常用到:?

用數(shù)組順序存儲(chǔ)二叉樹(shù), 其顯著特征是: (n表示數(shù)組的索引值)?

  • 在數(shù)組中index=n的元素對(duì)應(yīng)的父節(jié)點(diǎn)為 index=(n-1)/2的元素, 向下取整;
  • 在數(shù)組中index=n的元素對(duì)應(yīng)的左子節(jié)點(diǎn)為 index=2*n+1的元素;
  • 在數(shù)組中index=n的元素對(duì)應(yīng)的右子節(jié)點(diǎn)為 index=2*n+2的元素;

如果此時(shí)給我們一個(gè)順序存儲(chǔ)的數(shù)組,叫我們構(gòu)建一顆二叉樹(shù)

給定二叉樹(shù)?[3,9,20,null,null,15,7],

Definition for a binary tree node.public class TreeNode {int val;TreeNode left;TreeNode right;TreeNode(int x) { val = x; }} public static TreeNode createBT(int[] arr, int i) // 初始時(shí),傳入的i==0 {TreeNode root = null; // 定義根節(jié)點(diǎn)if (i >= arr.length) // i >= arr.length 時(shí),表示已經(jīng)到達(dá)了根節(jié)點(diǎn)return null;root = new TreeNode(arr[i]); // 根節(jié)點(diǎn)root.left = createBT(arr, 2*i+1); // 遞歸建立左孩子結(jié)點(diǎn)root.right = createBT(arr, 2*i+2); // 遞歸建立右孩子結(jié)點(diǎn)return root; }public class Main {public static void main(String[] args) {int[] arr = {3,9,20,null,null,15,7};TreeNode root = createBT(arr, 0);System.out.println("先序遍歷:");PreOrder(root);System.out.println("\n中序遍歷:");InOrder(root);System.out.println("\n后序遍歷:");PostOrder(root);} // 這里只寫(xiě)preOrder,中序和后序自己寫(xiě)下public static void preOrder(TreeNode root){if (root==null){return;}System.out.println(root.val);preOrder(root.left) ;preOrder(root.right);}

至于二叉樹(shù)的遍歷比較簡(jiǎn)單:遞歸就完事兒

// 先序遍歷public static void PreOrder(TreeNode root){if (root == null)return;System.out.print(root.val+" ");PreOrder(root.left);PreOrder(root.right);} // 中序遍歷public static void InOrder(TreeNode root){if (root == null)return;InOrder(root.left);System.out.print(root.val+" ");InOrder(root.right);} // 后序遍歷public static void PostOrder(TreeNode root){if (root == null)return;PostOrder(root.left);PostOrder(root.right);System.out.print(root.val+" ");}

接下來(lái)我們來(lái)介紹一下二叉樹(shù)的種類(lèi):

1、滿(mǎn)二叉樹(shù)

所有葉結(jié)點(diǎn)同處于最底層(非底層結(jié)點(diǎn)均是內(nèi)部結(jié)點(diǎn)),一個(gè)深度為k(>=-1)且有2^(k+1) - 1個(gè)結(jié)點(diǎn)。如圖(圖來(lái)源于veil的博客):

2、完全二叉樹(shù)

葉結(jié)點(diǎn)只能出現(xiàn)在最底層的兩層,且最底層葉結(jié)點(diǎn)均處于次底層葉結(jié)點(diǎn)的左側(cè)。設(shè)二叉樹(shù)的深度為h,除第 h 層外,其它各層 (1~h-1) 的結(jié)點(diǎn)數(shù)都達(dá)到最大個(gè)數(shù),第 h 層所有的結(jié)點(diǎn)都連續(xù)集中在最左邊。

?一個(gè)完全二叉樹(shù)的初始化和遍歷:

我們來(lái)一道力扣題目:

完全二叉樹(shù)是每一層(除最后一層外)都是完全填充(即,節(jié)點(diǎn)數(shù)達(dá)到最大)的,并且所有的節(jié)點(diǎn)都盡可能地集中在左側(cè)。

設(shè)計(jì)一個(gè)用完全二叉樹(shù)初始化的數(shù)據(jù)結(jié)構(gòu)?CBTInserter,它支持以下幾種操作:

CBTInserter(TreeNode root)?使用頭節(jié)點(diǎn)為?root?的給定樹(shù)初始化該數(shù)據(jù)結(jié)構(gòu);
CBTInserter.insert(int v)??向樹(shù)中插入一個(gè)新節(jié)點(diǎn),節(jié)點(diǎn)類(lèi)型為 TreeNode,值為 v 。使樹(shù)保持完全二叉樹(shù)的狀態(tài),并返回插入的新節(jié)點(diǎn)的父節(jié)點(diǎn)的值;
CBTInserter.get_root() 將返回樹(shù)的頭節(jié)點(diǎn)。
?

示例 1:

輸入:inputs = ["CBTInserter","insert","get_root"], inputs = [[[1]],[2],[]]
輸出:[null,1,[1,2]]

初始化一個(gè)完全二叉樹(shù),然后做一個(gè)前序遍歷的代碼

public class CBTInserter {private static TreeNode root;//輸入就是一棵樹(shù)public CBTInserter(TreeNode root) {this.root = root;}public int insert(int val) {TreeNode newNode = new TreeNode(val);// 我最開(kāi)始這么寫(xiě)的,給的一個(gè)測(cè)試用例也通過(guò)了,但是想想有什么問(wèn)題Deque<TreeNode> stack =new LinkedList<>();if (root==null){root = newNode;}stack.push(root);TreeNode node=null;while (stack!=null && stack.size()>0){node= stack.pop();if (node.left==null){node.left =newNode;}else if (node.right==null){node.right = newNode;}else if (node.left!=null){stack.push(node.left);}else if (node.right !=null){stack.push(node.right);}}return node.val;}public TreeNode get_root() {return root;}public static void main(String[] args) {int[] arr = {1,2,3,4,5,6}; //這就是不通過(guò)的,實(shí)際上我并沒(méi)有找到最小的TreeNode root =CreateTree.createBT(arr, 0);//這個(gè)前面有CBTInserter obj = new CBTInserter(root);obj.insert(7);obj.insert(8);obj.get_root();} }

思路:回想一下我們的目的

將所有節(jié)點(diǎn)編號(hào),按照從上到下從左到右的順序。

實(shí)際上我們希望,在每個(gè)插入步驟中,我們希望插入到一個(gè)編號(hào)最小的節(jié)點(diǎn)(并且它的字樹(shù)有空閑,那邊沒(méi)有我就插哪里)。

那么我們可以利用二叉樹(shù)的性質(zhì),想到?jīng)]?我們只要找到我要新插入節(jié)點(diǎn)的父節(jié)點(diǎn)實(shí)際上就解決問(wèn)題了 我們很容易得到父節(jié)點(diǎn)在數(shù)組中的下標(biāo)是這個(gè)n/2 -1,不會(huì)的自己手畫(huà)一下就推出來(lái)了

// 實(shí)際上你用一個(gè) int[]即可 class CBTInserter {private List<TreeNode> array = new LinkedList<>();public CBTInserter(TreeNode root) {Queue<TreeNode> queue = new LinkedList<>();queue.add(root);while (!queue.isEmpty()){TreeNode treeNode = queue.remove();if (treeNode.left != null) {queue.add(treeNode.left);}if (treeNode.right != null) {queue.add(treeNode.right);}array.add(treeNode);}}public int insert(int v) {TreeNode node = new TreeNode(v);array.add(node);TreeNode p = array.get(array.size() / 2-1);if (p.left == null) {p.left = node;}else {p.right = node;}return p.val;}public TreeNode get_root() {return array.get(0);}}

?實(shí)際上按照剛剛的思路其實(shí)也能做出來(lái)

讓我們來(lái)改造下:

// 就是力扣的官方答案了。 bfs將最編號(hào)并且子樹(shù)有一個(gè)部位null的取到 class CBTInserter {TreeNode root;Deque<TreeNode> deque;public CBTInserter(TreeNode root) {this.root = root;deque = new LinkedList();Queue<TreeNode> queue = new LinkedList();queue.offer(root);// BFS to populate dequewhile (!queue.isEmpty()) {TreeNode node = queue.poll();if (node.left == null || node.right == null)deque.offerLast(node);if (node.left != null)queue.offer(node.left);if (node.right != null)queue.offer(node.right);}}public int insert(int v) {TreeNode node = deque.peekFirst();deque.offerLast(new TreeNode(v));if (node.left == null)node.left = deque.peekLast();else {node.right = deque.peekLast();deque.pollFirst();}return node.val;}public TreeNode get_root() {return root;} }作者:LeetCode 鏈接:https://leetcode-cn.com/problems/complete-binary-tree-inserter/solution/wan-quan-er-cha-shu-cha-ru-qi-by-leetcode/ 來(lái)源:力扣(LeetCode) 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

3、平衡二叉樹(shù)

?平衡二叉樹(shù)(Self-balancing binary search tree)又被稱(chēng)為AVL樹(shù)(有別于AVL算法),且具有以下性質(zhì):它是一 棵空樹(shù)或它的左右兩個(gè)子樹(shù)的高度差的絕對(duì)值不超過(guò)1,并且左右兩個(gè)子樹(shù)都是一棵平衡二叉樹(shù)。平衡二叉樹(shù)的常用實(shí)現(xiàn)方法有紅黑樹(shù)、AVL、替罪羊樹(shù)、Treap、伸展樹(shù)等。 最小二叉平衡樹(shù)的節(jié)點(diǎn)的公式如下 F(n)=F(n-1)+F(n-2)+1 這個(gè)類(lèi)似于一個(gè)遞歸的數(shù)列,可以參考Fibonacci(斐波那契)數(shù)列,1是根節(jié)點(diǎn),F(n-1)是左子樹(shù)的節(jié)點(diǎn)數(shù)量,F(n-2)是右子樹(shù)的節(jié)點(diǎn)數(shù)量。(百度百科)

關(guān)于平衡二叉樹(shù)可以查看這個(gè)博客:平衡二叉樹(shù)詳解 - zhangbaochong - 博客園

主要是清楚平衡二叉樹(shù)的幾種情況

1.對(duì)α的左兒子的左子樹(shù)進(jìn)行一次插入

2.對(duì)α的左兒子的右子樹(shù)進(jìn)行一次插入

3.對(duì)α的右兒子的左子樹(shù)進(jìn)行一次插入

4.對(duì)α的右兒子的右子樹(shù)進(jìn)行一次插入

情形1和情形4是關(guān)于α的鏡像對(duì)稱(chēng),二情形2和情形3也是關(guān)于α的鏡像對(duì)稱(chēng),因此理論上看只有兩種情況,但編程的角度看還是四種情形。

第一種情況是插入發(fā)生在“外邊”的情形(左左或右右),該情況可以通過(guò)一次單旋轉(zhuǎn)完成調(diào)整;第二種情況是插入發(fā)生在“內(nèi)部”的情形(左右或右左),這種情況比較復(fù)雜,需要通過(guò)雙旋轉(zhuǎn)來(lái)調(diào)。

那么旋轉(zhuǎn)究竟是做個(gè)什么事情??針對(duì)四種情況的旋轉(zhuǎn)算法都不同。下面是針對(duì)左左情況的單旋轉(zhuǎn)算法的步驟

如果給你一個(gè)順序數(shù)組,如何來(lái)構(gòu)建成一棵樹(shù)呢?

來(lái)實(shí)戰(zhàn)一下
從上到下打印二叉樹(shù)

從上到下打印出二叉樹(shù)的每個(gè)節(jié)點(diǎn),同一層的節(jié)點(diǎn)按照從左到右的順序打印。

如:
給定二叉樹(shù):?[3,9,20,null,null,15,7],

? ? 3
? ?/ \
? 9 ?20
? ? / ?\
? ?15 ? 7
返回:

[3,9,20,15,7]

分析一下,從上到下,即代表從根節(jié)點(diǎn)開(kāi)始。從左到右,那就是從左子樹(shù)到右子樹(shù)。想到了什么?

樹(shù)的前序遍歷對(duì)不對(duì)?所謂的前序、中序、后序不過(guò)是訪問(wèn)根節(jié)點(diǎn)的順序。那么思路很簡(jiǎn)單了,

樹(shù)的變種

字典樹(shù)(前綴樹(shù)題目)

前綴樹(shù)的定義:
又稱(chēng)單詞查找樹(shù),字典樹(shù),Trie樹(shù),是一種樹(shù)形結(jié)構(gòu),是一種哈希樹(shù)的變種。典型應(yīng)用是用于統(tǒng)計(jì),排序和保存大量的字符串(但不僅限于字符串),所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計(jì)。它的優(yōu)點(diǎn)是:利用字符串的公共前綴來(lái)減少查詢(xún)時(shí)間,最大限度地減少無(wú)謂的字符串比較,查詢(xún)效率比哈希樹(shù)高。

前綴樹(shù)的性質(zhì):

1)根節(jié)點(diǎn)不包含字符,除根節(jié)點(diǎn)外每一個(gè)節(jié)點(diǎn)都只包含一個(gè)字符
2)從根節(jié)點(diǎn)到某一節(jié)點(diǎn),路徑上經(jīng)過(guò)的字符連接起來(lái),為該節(jié)點(diǎn)對(duì)應(yīng)的字符串
3)每個(gè)節(jié)點(diǎn)的所有子節(jié)點(diǎn)包含的字符都不相同
注:每個(gè)節(jié)點(diǎn)都含有26個(gè)鏈接表示出現(xiàn)的26個(gè)小寫(xiě)字母,即每個(gè)節(jié)點(diǎn)表示的字符是26個(gè)字符中的一個(gè),當(dāng)字符串插入完成時(shí),我們就會(huì)標(biāo)記該字符串就是完整的字符串了。
?

添加與搜索單詞 - 數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)

請(qǐng)你設(shè)計(jì)一個(gè)數(shù)據(jù)結(jié)構(gòu),支持 添加新單詞 和 查找字符串是否與任何先前添加的字符串匹配 。

實(shí)現(xiàn)詞典類(lèi) WordDictionary :

WordDictionary() 初始化詞典對(duì)象
void addWord(word) 將 word 添加到數(shù)據(jù)結(jié)構(gòu)中,之后可以對(duì)它進(jìn)行匹配
bool search(word) 如果數(shù)據(jù)結(jié)構(gòu)中存在字符串與?word 匹配,則返回 true ;否則,返回??false 。word 中可能包含一些 '.' ,每個(gè)?. 都可以表示任何一個(gè)字母。
?

示例:

輸入:
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
輸出:
[null,null,null,null,false,true,true,true]

解釋:
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("bad");
wordDictionary.addWord("dad");
wordDictionary.addWord("mad");
wordDictionary.search("pad"); // return False
wordDictionary.search("bad"); // return True
wordDictionary.search(".ad"); // return True
wordDictionary.search("b.."); // return True
?

提示:

1 <= word.length <= 500
addWord 中的 word 由小寫(xiě)英文字母組成
search 中的 word 由 '.' 或小寫(xiě)英文字母組成
最多調(diào)用 50000 次 addWord 和 search

思路分析:

非常明顯的一道前綴樹(shù)的問(wèn)題,我們只需要構(gòu)造一棵前綴樹(shù),然后每個(gè)一次去遍歷這棵樹(shù)就行了 怎么實(shí)現(xiàn)呢? 第一:需要有一個(gè)根節(jié)點(diǎn);每次從根節(jié)點(diǎn)開(kāi)始遍歷取出單詞的每一個(gè)單詞,從根開(kāi)始遍歷,單詞的word,是否是這個(gè)單詞的結(jié)束 那么我們需要先構(gòu)造一個(gè)樹(shù)的節(jié)點(diǎn)的這樣的數(shù)據(jù)結(jié)構(gòu):包含了下一個(gè)節(jié)點(diǎn)和單詞是都結(jié)束的標(biāo)識(shí)

好了直接上代碼了:

public class WordDictionary {/** Initialize your data structure here. */public class ThreeNode {Map<Character,ThreeNode> childdren;Boolean wordEnd;public ThreeNode(){childdren = new HashMap<Character, ThreeNode>();wordEnd = false;}}private ThreeNode root;public WordDictionary() {root = new ThreeNode();root.wordEnd = false;}public void addWord(String word) {ThreeNode temp = root;// 取到根節(jié)點(diǎn)if (!word.isEmpty()){for (int i = 0; i < word.length(); i++) {if (!temp.childdren.containsKey( word.charAt(i)))temp.childdren.put(word.charAt(i),new ThreeNode());temp = temp.childdren.get(word.charAt(i));//這一步很重要,表示繼續(xù)從下一個(gè)節(jié)點(diǎn)開(kāi)始遍歷}//遍歷完將這個(gè)節(jié)點(diǎn)單詞置為結(jié)束temp.wordEnd =true;}}public boolean search(String word) {ThreeNode temp = root;// 取到根節(jié)點(diǎn)boolean isFound =true;if (!word.isEmpty()){for (int i = 0; i < word.length(); i++) {//注意到題目中有個(gè)要求就是點(diǎn)號(hào)可以代替任何字母if (!temp.childdren.containsKey( word.charAt(i)) && !(word.charAt(i) =='.'))//搜索和插入很類(lèi)似// temp.childdren.put(word.charAt(i),new ThreeNode());return false;temp = temp.childdren.get(word.charAt(i));//這一步很重要,表示繼續(xù)從下一個(gè)節(jié)點(diǎn)開(kāi)始遍歷}}//解釋一下 題目是說(shuō)的輸入的字符串完全匹配。所以必須要跟上字符串是否結(jié)束。如果是包含則直接返回了return isFound && temp.wordEnd;} }

總結(jié)

以上是生活随笔為你收集整理的算法【一】树的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。