目錄
1.3.1InsertAtEnd - 在鏈表的末尾插入指定元素
1.3.2InsertAtHead - 在鏈接列表的開頭/頭部插入指定元素
1.3.3Delete - 從鏈接列表中刪除指定元素(指定位置)
1.3.4Delete - 從鏈接列表中刪除指定元素Node
2.2.1平衡二叉樹Balanced Binary Tree
1.鏈表
鏈表是另一個重要的線性數據結構,乍一看可能有點像數組,但在內存分配、內部結構以及數據插入和刪除的基本操作方面均有所不同。
鏈表就像一個節點鏈,其中每個節點包含着數據和指向後續節點的指針。 鏈表還包含一個頭指針,它指向鏈表的第一個元素,但當列表爲空時,它指向null或無具體內容。
1.1單鏈表
單鏈表包含內容和下一個元素的指針;
單鏈表結構圖:
單鏈表類定義:
class Node{
int val; //節點元素(內容)
Node next; //指向下一節點的引用
public Node(int val){
this.val = val;
}
}
1.2雙鏈表
雙鏈表包含內容,上一個元素指針和下一個元素的指針;包含單鏈表內容和下一個指針,多了上一個元素指針;
雙鏈表結構圖:
雙鏈表類定義:
class Node{
int val; //節點元素(內容)
Node pre; //指向上一個節點引用
Node next; //指向下一節點的引用
public Node(int val){
this.val = val;
}
}
1.3單鏈表常見操作
//鏈表
public class LinkNode {
Node head; //頭
// Node last;
/**
* 節點,包含節點元素(內容)和指向下一節點的引用
*/
static class Node{
int val; //節點元素(內容)
Node next; //指向下一節點的引用
public Node(int val){
this.val = val;
}
}
}
1.3.1InsertAtEnd - 在鏈表的末尾插入指定元素
//在鏈表的末尾插入指定元素
public void addNode(int d){
Node node = new Node(d);
if(head == null){
head = node;
return;
}
Node tmp = head;
while (tmp.next != null){
tmp = tmp.next;
}
tmp.next = node;
}
1.3.2InsertAtHead - 在鏈接列表的開頭/頭部插入指定元素
//在鏈接列表的開頭/頭部插入指定元素
public void addHeadNode(int d){
Node node = new Node(d);
if(head == null){
head = node;
return;
}
node.next = head;
head = node;
}
1.3.3Delete - 從鏈接列表中刪除指定元素(指定位置)
/**
* 從鏈表刪除第index(0-based)個位置的元素 ,返回刪除結果 (實際不常用,練習用)
* @param index
* @return
*/
public boolean deleteIndexNode(int index){
if(index<0 || index>=length()){
return false;
}
if(index == 0){
head = head.next;
}else {
Node pre = head;
for(int i=1; i<index; i++){
pre = pre.next; //獲取刪除節點的前一個節點
}
Node temp = pre.next; //保留要刪除節點
pre.next = temp.next; //刪除節點的next賦值給刪除節點的前一個節點next
temp.next = null; //刪除節點next設置爲null
}
return true;
}
1.3.4Delete - 從鏈接列表中刪除指定元素Node
/**
* 刪除指定節點
* @param node
* @return
*/
public boolean deleteNode(Node node){
if(node == null || head == null){
return false;
}
//不是最後一個節點
if(node.next != null){
Node q = node.next;
node.val = q.val;
node.next = q.next;
return true;
}else {
//最後節點
Node temp = head;
while (temp.next != null){
if(temp.next == node){
temp.next = node.next;
return true;
}
temp = temp.next;
}
}
return false;
}
1.3.5鏈表基礎操作獲取鏈表長度,是否鏈表爲空等
//鏈表長度
public int length(){
if(head == null){
return 0;
}
int length = 1;
Node tmp = head;
while (tmp.next != null){
length++;
tmp = tmp.next;
}
return length;
}
public Node getHead(){
return head;
}
/**
* 判斷鏈表是否爲空
* @return
*/
public boolean isEmpty(){
return head == null;
}
/**
* 打印節點
*/
public void printListNode(){
Node temp = head;
while (temp != null){
System.out.println(temp.val);
temp = temp.next;
}
}
1.4常見鏈表面試
1.4.1反轉鏈表
/**
* 反轉鏈表
* 1->2->3->4
* 1:當前節點,next節點變爲null(2做爲下一次的當前節點,1做爲下一次的next)
* 1->null
* 1:做爲next節點,2:做爲當前節點
* 2->1->null(3做爲下一次的當前節點,2做爲下一次的next)
* 2:做爲next節點,3:做爲當前節點
* 3->2->1->null
*
*/
public void reverseLinkNode(){
if(head == null || head.next == null){
return;
}
Node curNode = head; //當前節點
Node nextNode = null; //下一個節點
while (curNode != null){
Node next = curNode.next; //保留當前節點的下一個節點
curNode.next = nextNode; //更新當前節點的next
nextNode = curNode; //當前節點做爲next
curNode = next; //next做爲當前節點
}
head = nextNode; //更新head節點
}
1.4.2檢測鏈表中的循環
/**
* 檢測鏈表中的循環
* 你可以利用弗洛伊德的循環尋找算法,也被稱爲烏龜和兔子算法。
這個想法是有兩個引用列表,並以不同的速度移動它們。一個1節點向前移動一個,另一個2節點移動。
如果鏈表有一個循環,他們一定會見面
否則兩個引用(或他們next)中的任何一個都會變成null。
*/
public boolean circle(){
if(head == null) return false;
Node slow = head;
Node fast = head;
while (slow.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
return true;
}
}
return false;
}
1.4.3返回鏈表倒數第N個節點
/**
* 返回鏈表倒數第N個節點
*/
public Node getBackN(int n){
int length = length();
if(length<n) return null;
//查找正向的位置
int position = length-n+1;
int index = 1;
//查找對應位置的元素
Node temp = head;
while (temp != null){
if(index == position){
return temp;
}
temp = temp.next;
index++;
}
return null;
}
1.4.4刪除鏈表中的重複項
/**
* 刪除重複元素
* 進行雙重循環遍歷,外循環當前遍歷的結點爲cur,內循環從cur開始遍歷,相同則刪除
*/
public void delRepeatData(){
Node temp = head;
while (temp != null){
Node cur = temp; //當前元素
Node pre = cur; //當前元素做爲前置元素
while (pre.next != null){
Node q = pre.next;
if(cur.val == q.val){ //判斷當前元素是否和後面的元素重複
pre.next = q.next; //重複則跳過重複的元素
}else {
pre = q; //不重複找到下一個節點
}
}
temp = temp.next; //下一個當前元素
}
}
2.樹
在計算機科學中,二叉樹是每個結點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。
一棵深度爲k,且有2^k-1個結點的二叉樹,稱爲滿二叉樹。這種樹的特點是每一層上的結點數都是最大結點數。而在一棵二叉樹中,除最後一層外,若其餘層都是滿的,並且或者最後一層是滿的,或者是在右邊缺少連續若干結點,則此二叉樹爲完全二叉樹。具有n個結點的完全二叉樹的深度爲floor(log2n)+1。深度爲k的完全二叉樹,至少有2k-1個葉子結點,至多有2k-1個結點。
2.1二叉樹基本概念
二叉樹:二叉樹是每個節點最多有兩個子樹的樹結構。
根節點:一棵樹最上面的節點稱爲根節點。
父節點、子節點:如果一個節點下面連接多個節點,那麼該節點稱爲父節點,它下面的節點稱爲子節點。
葉子節點:沒有任何子節點的節點稱爲葉子節點。
兄弟節點:具有相同父節點的節點互稱爲兄弟節點。
節點度:節點擁有的子樹數。上圖中,13的度爲2,46的度爲1,28的度爲0。
樹的深度:從根節點開始(其深度爲0)自頂向下逐層累加的。上圖中,13的深度是1,30的深度是2,28的深度是3。
樹的高度:從葉子節點開始(其高度爲0)自底向上逐層累加的。54的高度是2,根節點23的高度是3。
對於樹中相同深度的每個節點來說,它們的高度不一定相同,這取決於每個節點下面的葉子節點的深度。上圖中,13和54的深度都是1,但是13的高度是1,54的高度是2。
2.2常見二叉樹
2.2.1平衡二叉樹Balanced Binary Tree
又被稱爲AVL樹,它是一顆空樹或左右兩個子樹的高度差的絕對值不超過 1,並且左右兩個子樹都是一棵平衡二叉樹。
2.2.2二叉搜索樹Binary Search Tree
又稱二叉查找樹、二叉排序樹(Binary Sort Tree)。它是一顆空樹或是滿足下列性質的二叉樹:
1)若左子樹不空,則左子樹上所有節點的值均小於或等於它的根節點的值;
2)若右子樹不空,則右子樹上所有節點的值均大於或等於它的根節點的值;
3)左、右子樹也分別爲二叉排序樹。
2.2.3紅黑樹Red Black Tree
是每個節點都帶有顏色屬性(顏色爲紅色或黑色)的自平衡二叉查找樹,滿足下列性質:
1)節點是紅色或黑色;
2)根節點是黑色;
3)所有葉子節點都是黑色;
4)每個紅色節點必須有兩個黑色的子節點。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點。)
5)從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點。
2.3二叉排序樹(二叉查找樹、二叉搜索樹)
有一下二叉查找樹,如圖:
在刪除節點的時候我們只需考慮一下三種情況:
(1)要刪除的節點是葉子結點,如圖:
(2)要刪除的節點有左節點但是沒有右節點,或者有右節點但是沒有左節點,如圖:
(3)要刪除的節點既有左節點又有右節點,在這種情況下,我們只需要將找到待刪節點的右子樹中值最小的節點,將其刪除並且獲取其值,並用其值替換待刪節點的值即可。如圖:
如上圖所示,如果要刪除節點7,則需尋找其右子樹中節點值最小的9,並且該值一定位於該右子樹的最左子節點;但是還有一種情況,如圖一右子樹沒有左節點,但是隻有右節點,這種情況就回到了前面的第二種情況。
具體代碼如下:注意Node類是一個內部類,在使用時注意方法。
2.3.1二叉排序樹定義
public class BinarySortTree {
Node root;
public class Node{
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
}
}
2.3.2添加節點
Node
public void addNode(Node node){
if(node == null){
return;
}
//添加到右子樹(插入值大於跟節點,則插入右側)
if(node.value>value){
if(right == null){
right = node;
}else {
right.addNode(node);
}
}else{
//添加到左子樹
if(left == null){
left = node;
}else {
left.addNode(node);
}
}
}
2.3.3二叉樹遍歷
Node
/**
* 若二叉樹非空,則依次執行如下操作:
⑴ 訪問根結點;
⑵ 遍歷左子樹;
⑶ 遍歷右子樹。
*/
public void preOrder(Node node){
if(node == null){
return;
}
System.out.println(node.value);//訪問跟節點
preOrder(node.left); //訪問左子樹
preOrder(node.right); //訪問右子樹
}
/**
* 若二叉樹非空,則依次執行如下操作:
⑴遍歷左子樹;
⑵訪問根結點;
⑶遍歷右子樹。
*/
public void middleOrder(Node node){
if(node == null){
return;
}
middleOrder(node.left); //訪問左子樹
System.out.println(node.value); //訪問跟節點
middleOrder(node.right); //訪問右子樹
}
/**
* 若二叉樹非空,則依次執行如下操作:
⑴遍歷左子樹;
⑵遍歷右子樹;
⑶訪問根結點。
*/
public void postOrder(Node node){
if(node == null){
return;
}
postOrder(node.left); //訪問左子樹
postOrder(node.right); //訪問右子樹
System.out.println(node.value); //訪問跟節點
}
2.3.4二叉樹查找
Node
/**
* 查找節點
* @param value
* @return
*/
public Node searchNode(int value){
if(this.value == value){ //返回節點
return this;
}
if(this.value>value){//查找節點值,小於當前節點值,從左側繼續查找
if(this.left == null) return null;
return this.left.searchNode(value);
}else {
if(this.right == null) return null;
return this.right.searchNode(value);
}
}
/**
* 求二叉樹的深度
*/
public int length(Node node){
if(node == null) return 0; //遞歸結束,空子樹高度爲0
int leftlength = length(node.left); //左子樹高度
int rightlength = length(node.right); //右子樹高度
//高度應該算更高的一邊,(+1是因爲要算上自身這一層)
if(leftlength>rightlength){
return leftlength+1;
}else {
return rightlength+1;
}
}
/**
* 查找父親節點
* @param value
* @return
*/
public Node searchParent(int value){
if(this.value == value){
return null;
}
if(this.left.value == value || this.right.value == value){
return this;//返回父節點
}
if(this.value>value){//查找節點值,小於當前節點值,左側查找
return this.left.searchParent(value);
}else {
return this.right.searchParent(value);
}
}
2.3.5二叉樹刪除
Node
/**
* 刪除指定節點
* @param value
*/
public void deleteNode(int value){
Node target = searchNode(value);
Node parent = searchParent(value);
if(target == null) return;
//1.葉子節點,直接將葉子節點設置爲null
if(target.left == null && target.right == null){
if(parent.left.value == target.value){
parent.left = null;
}else {
parent.right = null;
}
}
//2.非葉子節點,有子節點
else if(target.left != null && target.right != null){
//2.1若刪除的是有左右節點
int min = minDelete(target.right);
target.value = min;
//2.2刪除只有左或者右節點
}else {
//目標節點左子樹不爲空
if(target.left != null){
//刪除父節點的左節點
if(target.value == parent.left.value){
parent.left = target.left;
}else {
//刪除父節點的右節點
parent.right = target.left;
}
//目標節點右子樹不爲空
}else{
//刪除父節點的左節點
if(target.value == parent.left.value){
parent.left = target.right;
}else {
//刪除父節點的右節點
parent.right = target.right;
}
}
}
// 20
// 9 21
// 7 12
// 5 8 11 13
// 13 14
}
/*
刪除node下最小值節點
*/
public int minDelete(Node node){
Node target = node;
while (target.left != null){
target = target.left;
}
deleteNode(target.value);
return target.value;
}
}
2.4紅黑樹Red Black Tree
a.紅黑樹的五個規則
是每個節點都帶有顏色屬性(顏色爲紅色或黑色)的自平衡二叉查找樹,滿足下列性質:
1)節點是紅色或黑色;
2)根節點是黑色;
3)所有葉子節點都是黑色;
4)每個紅色節點必須有兩個黑色的子節點。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點。)
5)從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點。
注意:新插入的節點顏色總是紅色的,這是因爲插入一個紅色節點比插入一個黑色節點違背紅-黑規則的可能性更小,原因是插入黑色節點總會改變黑色高度(違背規則4),但是插入紅色節點只有一半的機會會違背規則3(因爲父節點是黑色的沒事,父節點是紅色的就違背規則3)。另外違背規則3比違背規則4要更容易修正。當插入一個新的節點時,可能會破壞這種平衡性,那麼紅-黑樹是如何修正的呢?
講的比較通俗易懂
https://www.cnblogs.com/ysocean/p/8004211.html#_label0
https://baijiahao.baidu.com/s?id=1641940303518144126&wfr=spider&for=pc
https://www.jianshu.com/p/e136ec79235c
參考:
https://www.cnblogs.com/sunshineliulu/p/7775063.html
https://blog.csdn.net/rodman177/article/details/89771156
https://segmentfault.com/a/1190000014743964
https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91%E9%81%8D%E5%8E%86/9796049?fr=aladdin