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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

判断数组中某个元素除自身外是否和其他数据不同_18 张图带你彻底认识这些数据结构...

發(fā)布時(shí)間:2025/3/15 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 判断数组中某个元素除自身外是否和其他数据不同_18 张图带你彻底认识这些数据结构... 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

作者 | 嘉明

來源 |?https://github.com/reng99/blogs

數(shù)據(jù)結(jié)構(gòu)是計(jì)算機(jī)存儲(chǔ)、組織數(shù)據(jù)的方式。數(shù)據(jù)結(jié)構(gòu)是指相互直接存在一種或多種特殊關(guān)系的數(shù)據(jù)元素的集合。通常情況下,精心選擇數(shù)據(jù)結(jié)構(gòu)可以帶來更高的運(yùn)行或者存儲(chǔ)效率。作為一名程序猿,更需要了解下數(shù)據(jù)結(jié)構(gòu)。

講到數(shù)據(jù)結(jié)構(gòu),我們都會(huì)談到線性結(jié)構(gòu)和非線性結(jié)構(gòu)。

1.線性結(jié)構(gòu)是一個(gè)有序數(shù)據(jù)元素的集合。它應(yīng)該滿足下面的特征:

  • 集合中必存在唯一的一個(gè)“第一個(gè)元素”

  • 集合中必存在唯一的一個(gè)“最后的元素”

  • 除最后一元素之外,其它數(shù)據(jù)元素均有唯一的“后繼”

  • 除第一個(gè)元素之外,其它數(shù)據(jù)元素均有唯一的“前驅(qū)”

按照百度百科的定義,我們知道符合條件的數(shù)據(jù)結(jié)構(gòu)就有棧、隊(duì)列和其它。

2.非線性結(jié)構(gòu)其邏輯特征是一個(gè)節(jié)點(diǎn)元素可以有多個(gè)直接前驅(qū)或多個(gè)直接后繼。

那么,符合條件的數(shù)據(jù)結(jié)構(gòu)就有圖、樹和其它。

嗯~了解一下就行。我們進(jìn)入正題:

數(shù)組

數(shù)組是一種線性結(jié)構(gòu),以十二生肖(鼠、牛、虎、兔、龍、蛇、馬、羊、猴、雞、狗、豬)排序?yàn)槔?#xff1a;

array_demo

我們來創(chuàng)建一個(gè)數(shù)組并打印出結(jié)果就一目了然了:

let?arr?=?['鼠',?'牛',?'虎',?'兔',?'龍',?'蛇',?'馬',?'羊',?'猴',?'雞',?'狗',?'豬'];
arr.forEach((item,?index)?=>?{
????console.log(`[?${index}?]?=>?${item}`);
});

//?[?0?]?=>?鼠
//?[?1?]?=>?牛
//?[?2?]?=>?虎
//?[?3?]?=>?兔
//?[?4?]?=>?龍
//?[?5?]?=>?蛇
//?[?6?]?=>?馬
//?[?7?]?=>?羊
//?[?8?]?=>?猴
//?[?9?]?=>?雞
//?[?10?]?=>?狗
//?[?11?]?=>?豬

是一種后進(jìn)先出(LIFO)線性表,是一種基于數(shù)組的數(shù)據(jù)結(jié)構(gòu)。(ps:其實(shí)后面講到的數(shù)據(jù)結(jié)構(gòu)或多或少有數(shù)組的影子)

  • LIFO(Last In First Out)表示后進(jìn)先出,后進(jìn)來的元素第一個(gè)彈出棧空間。類似于自動(dòng)餐托盤,最后放上去的托盤,往往先被拿出來使用。

  • 僅允許在表的一端進(jìn)行插入和移除元素。這一端被稱為棧頂,相對(duì)地,把另一端稱為棧底。如下圖的標(biāo)識(shí)。

  • 向一個(gè)棧插入新元素稱作進(jìn)棧、入?;驂簵?/strong>,這是將新元素放在棧頂元素上面,使之成為新的棧頂元素。

  • 從一個(gè)棧刪除元素又稱為出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。

stack_demo

我們代碼寫下,熟悉下棧:

class?Stack?{
????constructor(){
????????this.items?=?[];
????}
????//?入棧操作
????push(element?=?''){
????????if(!element)?return;
????????this.items.push(element);
????????return?this;
????}
????//?出棧操作
????pop(){
????????this.items.pop();
????????return?this;
????}
????//?對(duì)棧一瞥,理論上只能看到棧頂或者說即將處理的元素
????peek(){
????????return?this.items[this.size()?-?1];
????}
????//?打印棧數(shù)據(jù)
????print(){
????????return?this.items.join('?');
????}
????//?棧是否為空
????isEmpty(){
????????return?this.items.length?==?0;
????}
????//?返回棧的元素個(gè)數(shù)
????size(){
????????return?this.items.length;
????}
}

?? 注意:棧這里的push和pop方法要和數(shù)組方法的push和pop方法區(qū)分下。

隊(duì)列

隊(duì)列是一種先進(jìn)先出(FIFO)受限的線性表。受限體現(xiàn)在于其允許在表的前端(front)進(jìn)行刪除操作,在表的末尾(rear)進(jìn)行插入【優(yōu)先隊(duì)列這些排除在外】操作。

queue_demo

代碼走一遍:

class?Queue?{
????constructor(){
????????this.items?=?[];
????}
????//?入隊(duì)操作
????enqueue(element?=?''){
????????if(!element)?return;
????????this.items.push(element);
????????return?this;
????}
????//?出隊(duì)操作
????dequeue(){
????????this.items.shift();
????????return?this;
????}
????//?查看隊(duì)前元素或者說即將處理的元素
????front(){
????????return?this.items[0];
????}
????//?查看隊(duì)列是否為空
????isEmpty(){
????????return?this.items.length?==?0;
????}
????//?查看隊(duì)列的長度
????len(){
????????return?this.items.length;
????}
????//?打印隊(duì)列數(shù)據(jù)
????print(){
????????return?this.items.join('?');
????}
}

鏈表

在進(jìn)入正題之前,我們先來聊聊數(shù)組的優(yōu)缺點(diǎn)。

優(yōu)點(diǎn):

  • 存儲(chǔ)多個(gè)元素,比較常用

  • 訪問便捷,使用下標(biāo)[index]即可訪問

缺點(diǎn):

  • 數(shù)組的創(chuàng)建通常需要申請(qǐng)一段連續(xù)的內(nèi)存空間,并且大小是固定的(大多數(shù)的編程語言數(shù)組都是固定的),所以在進(jìn)行擴(kuò)容的時(shí)候難以掌控。(一般情況下,申請(qǐng)一個(gè)更大的數(shù)組,會(huì)是之前數(shù)組的倍數(shù),比如兩倍。然后,再將原數(shù)組中的元素復(fù)制過去)

  • 插入數(shù)據(jù)越是靠前,其成本很高,因?yàn)樾枰M(jìn)行大量元素的位移。

相對(duì)數(shù)組,鏈表亦可以存儲(chǔ)多個(gè)元素,而且存儲(chǔ)的元素在內(nèi)容中不必是連續(xù)的空間;在插入和刪除數(shù)據(jù)時(shí),時(shí)間復(fù)雜度可以達(dá)到O(1)。在查找元素的時(shí)候,還是需要從頭開始遍歷的,比數(shù)組在知道下表的情況下要快,但是數(shù)組如果不確定下標(biāo)的話,那就另說了…

我們使用十二生肖來了解下鏈表:

linklist_demo

鏈表是由一組節(jié)點(diǎn)組成的集合。每個(gè)節(jié)點(diǎn)都使用一個(gè)對(duì)象的引用指向它的后繼。如上圖。下面用代碼實(shí)現(xiàn)下:

//?鏈表
class?Node?{
????constructor(element){
????????this.element?=?element;
????????this.next?=?null;
????}
}

class?LinkedList?{
????constructor(){
????????this.length?=?0;?//?鏈表長度
????????this.head?=?new?Node('head');?//?表頭節(jié)點(diǎn)
????}
????/**
?????*?@method?find?查找元素的功能,找不到的情況下直接返回鏈尾節(jié)點(diǎn)
?????*?@param?{?String?}?item?要查找的元素
?????*?@return?{?Object?}?返回查找到的節(jié)點(diǎn)?
?????*/
????find(item?=?''){
????????let?currNode?=?this.head;
????????while(currNode.element?!=?item?&&?currNode.next){
????????????currNode?=?currNode.next;
????????}
????????return?currNode;
????}
????/**
????*?@method?findPrevious?查找鏈表指定元素的前一個(gè)節(jié)點(diǎn)
????*?@param?{?String?}?item?指定的元素
????*?@return?{?Object?}?返回查找到的之前元素的前一個(gè)節(jié)點(diǎn),找不到節(jié)點(diǎn)的話返回鏈尾節(jié)點(diǎn)
????*/
????findPrevious(item){
????????let?currNode?=?this.head;
????????while((currNode.next?!=?null)?&&?(currNode.next.element?!=?item)){
????????????currNode?=?currNode.next;
????????}
????????return?currNode;
????}
????/**
?????*?@method?insert?插入功能
?????*?@param?{?String?}?newElement?要出入的元素
?????*?@param?{?String?}?item?想要追加在后的元素(此元素不一定存在)
?????*/
????insert(newElement?=?'',?item){
????????if(!newElement)?return;
????????let?newNode?=?new?Node(newElement),
????????????currNode?=?this.find(item);
????????newNode.next?=?currNode.next;
????????currNode.next?=?newNode;
????????this.length++;
????????return?this;
????}
????//?展示鏈表元素
????display(){
????????let?currNode?=?this.head,
????????????arr?=?[];
????????while(currNode.next?!=?null){
????????????arr.push(currNode.next.element);
????????????currNode?=?currNode.next;
????????}
????????return?arr.join('?');
????}
????//?鏈表的長度
????size(){
????????return?this.length;
????}
????//?查看鏈表是否為空
????isEmpty(){
????????return?this.length?==?0;
????}
????/**
?????*?@method?indexOf?查看鏈表中元素的索引
?????*?@param?{?String?}?element?要查找的元素
?????*/
????indexOf(element){
????????let?currNode?=?this.head,
????????????index?=?0;
????????while(currNode.next?!=?null){
????????????index++;
????????????if(currNode.next.element?==?element){
????????????????return?index;
????????????}
????????????currNode?=?currNode.next;
????????}
????????return?-1;
????}
????/**
?????*?@method?removeEl?移除指定的元素
?????*?@param?{?String?}?element?
?????*/
????removeEl(element){
????????let?preNode?=?this.findPrevious(element);
????????preNode.next?=?preNode.next?!=?null???preNode.next.next?:?null;
????}
}

字典

字典的主要特點(diǎn)是鍵值一一對(duì)應(yīng)的關(guān)系??梢员扔鞒晌覀儸F(xiàn)實(shí)學(xué)習(xí)中查不同語言翻譯的字典。這里字典的鍵(key)理論上是可以使用任意的內(nèi)容,但還是建議語意化一點(diǎn),比如下面的十二生肖圖:

dictionary_democlass?Dictionary?{
????constructor(){
????????this.items?=?{};
????}
????/**
?????*?@method?set?設(shè)置字典的鍵值對(duì)
?????*?@param?{?String?}?key?鍵
?????*?@param?{*}?value?值
?????*/
????set(key?=?'',?value?=?''){
????????this.items[key]?=?value;
????????return?this;
????}
????/**
?????*?@method?get?獲取某個(gè)值
?????*?@param?{?String?}?key?鍵
?????*/
????get(key?=?''){
????????return?this.has(key)???this.items[key]?:?undefined;
????}
????/**
?????*?@method?has?判斷是否含有某個(gè)鍵的值
?????*?@param?{?String?}?key?鍵
?????*/
????has(key?=?''){
????????return?this.items.hasOwnProperty(key);
????}
????/**
?????*?@method?remove?移除元素
?????*?@param?{?String?}?key?
?????*/
????remove(key){
????????if(!this.has(key))??return?false;
????????delete?this.items[key];
????????return?true;
????}
????//?展示字典的鍵
????keys(){
????????return?Object.keys(this.items).join('?');
????}
????//?字典的大小
????size(){
????????return?Object.keys(this.items).length;
????}
????//?展示字典的值
????values(){
????????return?Object.values(this.items).join('?');
????}
????//?清空字典
????clear(){
????????this.items?=?{};
????????return?this;
????}
}

集合

集合通常是由一組無序的,不能重復(fù)的元素構(gòu)成。 一些常見的集合操作如圖:

set_demo
es6中已經(jīng)封裝好了可用的Set類。我們手動(dòng)來寫下相關(guān)的邏輯://?集合
class?Set?{
????constructor(){
????????this.items?=?[];
????}
????/**
?????*?@method?add?添加元素
?????*?@param?{?String?}?element?
?????*?@return?{?Boolean?}
?????*/
????add(element?=?''){
????????if(this.items.indexOf(element)?>=?0)?return?false;
????????this.items.push(element);
????????return?true;
????}
????//?集合的大小
????size(){
????????return?this.items.length;
????}
????//?集合是否包含某指定元素
????has(element?=?''){
????????return?this.items.indexOf(element)?>=?0;
????}
????//?展示集合
????show(){
????????return?this.items.join('?');
????}
????//?移除某個(gè)元素
????remove(element){
????????let?pos?=?this.items.indexOf(element);
????????if(pos?0)?return?false;
????????this.items.splice(pos,?1);
????????return?true;
????}
????/**
?????*?@method?union?并集
?????*?@param?{?Array?}?set?數(shù)組集合
?????*?@return?{?Object?}?返回并集的對(duì)象
?????*/
????union(set?=?[]){
????????let?tempSet?=?new?Set();
????????for(let?i?=?0;?i?this.items.length;?i++){
????????????tempSet.add(this.items[i]);
????????}
????????for(let?i?=?0;?i?????????????if(tempSet.has(set.items[i]))?continue;
????????????tempSet.items.push(set.items[i]);
????????}
????????return?tempSet;
????}
????/**
?????*?@method?intersect?交集
?????*?@param?{?Array?}?set?數(shù)組集合
?????*?@return?{?Object?}?返回交集的對(duì)象
?????*/
????intersect(set?=?[]){
????????let?tempSet?=?new?Set();
????????for(let?i?=?0;?i?this.items.length;?i++){
????????????if(set.has(this.items[i])){
????????????????tempSet.add(this.items[i]);
????????????}
????????}
????????return?tempSet;
????}
????/**
?????*?@method?isSubsetOf?【A】是【B】的子集?
?????*?@param?{?Array?}?set?數(shù)組集合
?????*?@return?{?Boolean?}?返回真假值
?????*/
????isSubsetOf(set?=?[]){
????????if(this.size()?>?set.size())?return?false;
????????this.items.forEach*(item?=>?{
????????????if(!set.has(item))?return?false;
????????});
????????return?true;
????}
}

散列表/哈希表

散列是一種常用的存儲(chǔ)技術(shù),散列使用的數(shù)據(jù)結(jié)構(gòu)叫做散列表/哈希表。在散列表上插入、刪除和取用數(shù)據(jù)都非???#xff0c;但是對(duì)于查找操作來說卻效率低下,比如查找一組數(shù)據(jù)中的最大值和最小值。查找的這些操作得求助其它數(shù)據(jù)結(jié)構(gòu),比如下面要講的二叉樹。

切入個(gè)案例感受下哈希表:

假如一家公司有1000個(gè)員工, 現(xiàn)在我們需要將這些員工的信息使用某種數(shù)據(jù)結(jié)構(gòu)來保存起來。你會(huì)采用什么數(shù)據(jù)結(jié)構(gòu)呢?

  • 方案一:數(shù)組

  • 按照順序?qū)⑺袉T工信息依次存入一個(gè)長度為1000的數(shù)組中。每個(gè)員工的信息都保存在該數(shù)組的某個(gè)位置上。

  • 但是我們要查看某個(gè)員工的信息怎么辦呢?一個(gè)個(gè)查找嗎?不太好找。

  • 數(shù)組最大的優(yōu)勢(shì)是什么?通過下標(biāo)值獲取信息。

  • 所以為了可以通過數(shù)組快速定位到某個(gè)員工,最好給員工信息中添加一個(gè)員工編號(hào),而編號(hào)對(duì)應(yīng)的就是員工的下標(biāo)值。

  • 當(dāng)查找某個(gè)員工信息時(shí),通過員工號(hào)可以快速定位到員工的信息位置。

  • 方案二:鏈表

  • 鏈表對(duì)應(yīng)插入和刪除數(shù)據(jù)有一定的優(yōu)勢(shì)。

  • 但是對(duì)于獲取員工的信息,每次都必須從頭遍歷到尾,這種方式顯然不是特別適合我們這里。

  • 最終方案:

  • 這么看最終方案似乎就是數(shù)組了,但是數(shù)組還是有缺點(diǎn),什么缺點(diǎn)呢?

  • 假如我們想查看下張三這位員工的信息,但是我們不知道張三的員工編號(hào),怎么辦呢?

  • 當(dāng)然,我們可以問他的員工編號(hào)。但是我們每查找一個(gè)員工都是要問一下這個(gè)員工的編號(hào)嗎?不合適。【那我們還不如直接問他的信息嘞】

  • 能不能有一種辦法,讓張三的名字和他的員工編號(hào)產(chǎn)生直接的關(guān)系呢?

  • 也就是通過張三這個(gè)名字,我們就能獲取到他的索引值,而再通過索引值我們就能獲取張三的信息呢?

  • 這樣的方案已經(jīng)存在了,就是使用哈希函數(shù),讓某個(gè)key的信息和索引值對(duì)應(yīng)起來。

那么散列表的原理和實(shí)現(xiàn)又是怎樣的呢,我們來聊聊。

我們的哈希表是基于數(shù)組完成的,我們從數(shù)組這里切入解析下。數(shù)組可以通過下標(biāo)直接定位到相應(yīng)的空間,哈希表的做法就是類似的實(shí)現(xiàn)。哈希表把key(鍵)通過一個(gè)固定的算法函數(shù)(此函數(shù)稱為哈希函數(shù)/散列函數(shù))轉(zhuǎn)換成一個(gè)整型數(shù)字,然后就將該數(shù)字對(duì)數(shù)組長度進(jìn)行取余,取余結(jié)果就當(dāng)作數(shù)組的下標(biāo),將value(值)存儲(chǔ)在以該數(shù)字為下標(biāo)的數(shù)組空間里,而當(dāng)使用哈希表進(jìn)行查詢的時(shí)候,就是再次使用哈希函數(shù)將key轉(zhuǎn)換為對(duì)應(yīng)的數(shù)組下標(biāo),并定位到該空間獲取value。

結(jié)合下面的代碼,也許你會(huì)更容易理解:

//?哈希表
class?HashTable?{
????constructor(){
????????this.table?=?new?Array(137);
????}
????/**
?????*?@method?hashFn?哈希函數(shù)
?????*?@param?{?String?}?data?傳入的字符串
?????*?@return?{?Number?}?返回取余的數(shù)字
?????*/
????hashFn(data){
????????let?total?=?0;
????????for(let?i?=?0;?i?????????????total?+=?data.charCodeAt(i);
????????}
????????return?total?%?this.table.length;
????}
????/**
?????*?
?????*?@param?{?String?}?data?傳入的字符串
?????*/
????put(data){
????????let?pos?=?this.hashFn(data);
????????this.table[pos]?=?data;
????????return?this;
????}
????//?展示
????show(){
????????this.table?&&?this.table.forEach((item,?index)?=>?{
????????????if(item?!=?undefined){
????????????????console.log(index?+?'?=>?'?+?item);
????????????}
????????})
????}
????//?...獲取值get函數(shù)等看官感興趣的話自己補(bǔ)充測(cè)試?yán)?br />}

hashtable_demo

針對(duì)上面的問題,我們存儲(chǔ)數(shù)據(jù)的時(shí)候,產(chǎn)生沖突的話我們可以像下面這樣解決:

1. 線性探測(cè)法

當(dāng)發(fā)生碰撞(沖突)時(shí),線性探測(cè)法檢查散列表中的下一個(gè)位置【有可能非順序查找位置,不一定是下一個(gè)位置】是否為空。如果為空,就將數(shù)據(jù)存入該位置;如果不為空,則繼續(xù)檢查下一個(gè)位置,直到找到一個(gè)空的位置為止。該技術(shù)是基于一個(gè)事實(shí):每個(gè)散列表都有很多空的單元格,可以使用它們存儲(chǔ)數(shù)據(jù)。

2. 開鏈法

但是,當(dāng)發(fā)生碰撞時(shí),我們?nèi)稳幌M麑ey(鍵)存儲(chǔ)到通過哈希函數(shù)產(chǎn)生的索引位置上,那么我們可以使用開鏈法。開鏈法是指實(shí)現(xiàn)哈希表底層的數(shù)組中,每個(gè)數(shù)組元素又是一個(gè)新的數(shù)據(jù)結(jié)構(gòu),比如另一個(gè)數(shù)組(這樣結(jié)合起來就是二位數(shù)組了),鏈表等,這樣就能存儲(chǔ)多個(gè)鍵了。使用這種技術(shù),即使兩個(gè)key(鍵)散列后的值相同,依然是被保存在同樣的位置,只不過它們是被保存在另一個(gè)數(shù)據(jù)結(jié)構(gòu)上而已。以另一個(gè)數(shù)據(jù)結(jié)構(gòu)是數(shù)組為例,存儲(chǔ)的數(shù)據(jù)如下:

open_link_method

二叉查找樹

  • 樹的定義:

  • 樹(Tree):n(n <= 0)個(gè)節(jié)點(diǎn)構(gòu)成的有限集合。

    • 當(dāng)`n = 0`時(shí),稱為空樹;

    • 對(duì)任意一棵空樹`(n > 0)`,它具備以下性質(zhì):

    • 樹中有一個(gè)稱為根(Root)的特殊節(jié)點(diǎn),用`r(root)`表示;

    • 其余節(jié)點(diǎn)可分為`m(m > 0)`個(gè)互不相交的有限集`T1,T2,…Tm`,其中每個(gè)集合本省又是一棵樹,稱為原來樹的子樹(SubTree)

  • 注意:

    • 子樹之間`不可以相交`;

    • 除了根節(jié)點(diǎn)外,每個(gè)節(jié)點(diǎn)有且僅有一個(gè)父節(jié)點(diǎn);

    • 一個(gè)`N`個(gè)節(jié)點(diǎn)的樹有`N-1`條邊。

  • 樹的術(shù)語:

  • 節(jié)點(diǎn)的度(Degree):節(jié)點(diǎn)的子樹個(gè)數(shù)。

  • 樹的度:樹的所有節(jié)點(diǎn)中最大的度數(shù)(樹的度通常為節(jié)點(diǎn)個(gè)數(shù)的N-1)。

  • 葉節(jié)點(diǎn)(Leaf):度為0的節(jié)點(diǎn)(也稱葉子節(jié)點(diǎn))。

  • 父節(jié)點(diǎn)(Parent):有子樹的節(jié)點(diǎn)是其子樹的父節(jié)點(diǎn)。

  • 子節(jié)點(diǎn)(Child):若A節(jié)點(diǎn)是B節(jié)點(diǎn)的父節(jié)點(diǎn),則稱B節(jié)點(diǎn)是A節(jié)點(diǎn)的子節(jié)點(diǎn)。

  • 兄弟節(jié)點(diǎn)(Sibling):具有同一個(gè)父節(jié)點(diǎn)的各節(jié)點(diǎn)彼此是兄弟節(jié)點(diǎn)。

  • 路徑和路徑長度:從節(jié)點(diǎn)n1到nk的路徑為一個(gè)節(jié)點(diǎn)序列n1,n2,n3,…,nk,ni是ni+1的父節(jié)點(diǎn)。路徑所包含邊的個(gè)數(shù)為路徑長度。

  • 節(jié)點(diǎn)的層次(Level):規(guī)定根節(jié)點(diǎn)在第0層,它的子節(jié)點(diǎn)是第1層,子節(jié)點(diǎn)的子節(jié)點(diǎn)是第2層,以此類推。

  • 樹的深度(Depth):樹中所有節(jié)點(diǎn)中的最大層次是這棵樹的深度(因?yàn)樯厦媸菑牡?層開始,深度 = 第最大層數(shù) + 1)

如下圖:

tree_intro
  • 二叉樹的定義:

  • 二叉樹可以為空,也就是沒有節(jié)點(diǎn)

  • 二叉樹若不為空,則它是由根節(jié)點(diǎn)和稱為其左子樹TL和右子樹RT的兩個(gè)不相交的二叉樹組成

  • 二叉樹每個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)不允許超過兩個(gè)

  • 二叉樹的五種形態(tài):

  • 只有根節(jié)點(diǎn)

  • 只有左子樹

  • 只有右子樹

  • 左右子樹均有

對(duì)應(yīng)下圖(從左至右):

five_style_binary_tree

我們接下來要講的是二叉查找樹(BST,Binary Search Tree)二叉查找樹,也稱二叉搜索樹或二叉排序樹,是一種特殊的二叉樹,相對(duì)值較小的值保存在左節(jié)點(diǎn)中,較大的值保存在右節(jié)點(diǎn)中。二叉查找樹特殊的結(jié)構(gòu)使它能夠快速的進(jìn)行查找、插入和刪除數(shù)據(jù)。下面我們來實(shí)現(xiàn)下:

//?二叉查找樹
//?輔助節(jié)點(diǎn)類
class?Node?{
????constructor(data,?left,?right){
????????this.data?=?data;
????????this.left?=?left;
????????this.right?=?right;
????}
????//?展示節(jié)點(diǎn)信息
????show(){
????????return?this.data;
????}
}
class?BST?{
????constructor(){
????????this.root?=?null;
????}
????//?插入數(shù)據(jù)
????insert(data){
????????let?n?=?new?Node(data,?null,?null);
????????if(this.root?==?null){
????????????this.root?=?n;
????????}else{
????????????let?current?=?this.root,
????????????????parent?=?null;
????????????while(true){
????????????????parent?=?current;
????????????????if(data?????????????????????current?=?current.left;
????????????????????if(current?==?null){
????????????????????????parent.left?=?n;
????????????????????????break;
????????????????????}
????????????????}else{
????????????????????current?=?current.right;
????????????????????if(current?==?null){
????????????????????????parent.right?=?n;
????????????????????????break;
????????????????????}
????????????????}
????????????}
????????}
????????return?this;
????}
????//?中序遍歷
????inOrder(node){
????????if(!(node?==?null)){
????????????this.inOrder(node.left);
????????????console.log(node.show());
????????????this.inOrder(node.right);
????????}
????}
????//???先序遍歷
????preOrder(node){
????????if(!(node?==?null)){
????????????console.log(node.show());
????????????this.preOrder(node.left);
????????????this.preOrder(node.right);
????????}
????}
????//?后序遍歷
????postOrder(node){
????????if(!(node?==?null)){
????????????this.postOrder(node.left);
????????????this.postOrder(node.right);
????????????console.log(node.show());
????????}
????}
????//?獲取最小值
????getMin(){
????????let?current?=?this.root;
????????while(!(current.left?==?null)){
????????????current?=?current.left;
????????}
????????return?current.data;
????}
????//?獲取最大值
????getMax(){
????????let?current?=?this.root;
????????while(!(current.right?==?null)){
????????????current?=?current.right;
????????}
????????return?current.data;
????}
????//?查找給定的值
????find(data){
????????let?current?=?this.root;
????????while(current?!=?null){
????????????if(current.data?==?data){
????????????????return?current;
????????????}else?if(data?????????????????current?=?current.left;
????????????}else{
????????????????current?=?current.right;
????????????}
????????}
????????return?null;
????}
????//?移除給定的值
????remove(data){
????????root?=?this.removeNode(this.root,?data);
????????return?this;
????}
????//?移除給定值的輔助函數(shù)
????removeNode(node,?data){
????????if(node?==?null){
????????????return?null;
????????}
????????if(data?==?node.data){
????????????//?葉子節(jié)點(diǎn)
????????????if(node.left?==?null?&&?node.right?==?null){
????????????????return?null;?//?此節(jié)點(diǎn)置空
????????????}
????????????//?沒有左子樹
????????????if(node.left?==?null){
????????????????return?node.right;
????????????}
????????????//?沒有右子樹
????????????if(node.right?==?null){
????????????????return?node.left;
????????????}
????????????//?有兩個(gè)子節(jié)點(diǎn)的情況
????????????let?tempNode?=?this.getSmallest(node.right);?//?獲取右子樹
????????????node.data?=?tempNode.data;?//?將其右子樹的最小值賦值給刪除的那個(gè)節(jié)點(diǎn)值
????????????node.right?=?this.removeNode(node.right,?tempNode.data);?//?刪除指定節(jié)點(diǎn)的下的最小值,也就是置其為空
????????????return?node;
????????}else?if(data?????????????node.left?=?this.removeNode(node.left,?data);
????????????return?node;
????????}else{
????????????node.right?=?this.removeNode(node.right,?data);
????????????return?node;
????????}
????}
????//?獲取給定節(jié)點(diǎn)下的二叉樹最小值的輔助函數(shù)
????getSmallest(node){
????????if(node.left?==?null){
????????????return?node;
????????}else{
????????????return?this.getSmallest(node.left);
????????}
????}
}

看了上面的代碼之后,你是否有些懵圈呢?我們借助幾張圖來了解下,或許你就豁然開朗了。

在遍歷的時(shí)候,我們分為三種遍歷方法--先序遍歷,中序遍歷和后序遍歷:

travel_tree

刪除節(jié)點(diǎn)是一個(gè)比較復(fù)雜的操作,考慮的情況比較多:

  • 該節(jié)點(diǎn)沒有葉子節(jié)點(diǎn)的時(shí)候,直接將該節(jié)點(diǎn)置空;

  • 該節(jié)點(diǎn)只有左子樹,直接將該節(jié)點(diǎn)賦予左子樹

  • 該節(jié)點(diǎn)只有右子樹,直接將該節(jié)點(diǎn)賦予右子樹

  • 該節(jié)點(diǎn)左右子樹都有,有兩種方法可以處理

  • 方案一:從待刪除節(jié)點(diǎn)的左子樹找節(jié)點(diǎn)值最大的節(jié)點(diǎn)A,替換待刪除節(jié)點(diǎn)值,并刪除節(jié)點(diǎn)A

  • 方案二:從待刪除節(jié)點(diǎn)的右子樹找節(jié)點(diǎn)值最小的節(jié)點(diǎn)A,替換待刪除節(jié)點(diǎn)值,并刪除節(jié)點(diǎn)A【?上面的示例代碼中就是這種方案】

刪除兩個(gè)節(jié)點(diǎn)的圖解如下:

remove_tree_node

由邊的集合及頂點(diǎn)的集合組成。

我們來了解下圖的相關(guān)術(shù)語:

  • 頂點(diǎn):圖中的一個(gè)節(jié)點(diǎn)。

  • 邊:表示頂點(diǎn)和頂點(diǎn)之間的連線。

  • 相鄰頂點(diǎn):由一條邊連接在一起的頂點(diǎn)稱為相鄰頂點(diǎn)。

  • 度:一個(gè)頂點(diǎn)的度是相鄰頂點(diǎn)的數(shù)量。比如0頂點(diǎn)和其它兩個(gè)頂點(diǎn)相連,0頂點(diǎn)的度就是2

  • 路徑:路徑是頂點(diǎn)

    v1,v2...,vn

    的一個(gè)連續(xù)序列。

  • 簡(jiǎn)單路徑:簡(jiǎn)單路徑要求不包含重復(fù)的頂點(diǎn)。

  • 回路:第一個(gè)頂點(diǎn)和最后一個(gè)頂點(diǎn)相同的路徑稱為回路。

  • 有向圖和無向圖

  • 有向圖表示圖中的邊是有方向的。

  • 無向圖表示圖中的邊是無方向的。

  • 帶權(quán)圖和無權(quán)圖

  • 帶權(quán)圖表示圖中的邊有權(quán)重。

  • 無權(quán)圖表示圖中的邊無權(quán)重。

如下圖:

graph_concept_intro

圖可以用于現(xiàn)實(shí)中的很多系統(tǒng)建模,比如:

  • 對(duì)交通流量建模

  • 頂點(diǎn)可以表示街道的十字路口, 邊可以表示街道.

  • 加權(quán)的邊可以表示限速或者車道的數(shù)量或者街道的距離.

  • 建模人員可以用這個(gè)系統(tǒng)來判定最佳路線以及最可能堵車的街道.

圖既然這么方便,我們來用代碼實(shí)現(xiàn)下:

//?圖
class?Graph{
????constructor(v){
????????this.vertices?=?v;?//?頂點(diǎn)個(gè)數(shù)
????????this.edges?=?0;?//?邊的個(gè)數(shù)
????????this.adj?=?[];?//?鄰接表或鄰接表數(shù)組
????????this.marked?=?[];?//?存儲(chǔ)頂點(diǎn)是否被訪問過的標(biāo)識(shí)
????????this.init();
????}
????init(){
????????for(let?i?=?0;?i?this.vertices;?i++){
????????????this.adj[i]?=?[];
????????????this.marked[i]?=?false;
????????}
????}
????//?添加邊
????addEdge(v,?w){
????????this.adj[v].push(w);
????????this.adj[w].push(v);
????????this.edges++;
????????return?this;
????}
????//?展示圖
????showGraph(){
????????for(let?i?=?0;?i?this.vertices;?i++){
????????????for(let?j?=?0;?j?this.vertices;?j++){
????????????????if(this.adj[i][j]?!=?undefined){
????????????????????console.log(i?+'?=>?'?+?this.adj[i][j]);
????????????????}
????????????}
????????}
????}
????//?深度優(yōu)先搜索
????dfs(v){
????????this.marked[v]?=?true;
????????if(this.adj[v]?!=?undefined){
????????????console.log("visited?vertex:?"?+?v);
????????}
????????this.adj[v].forEach(w?=>?{
????????????if(!this.marked[w]){
????????????????this.dfs(w);
????????????}
????????})
????}
????//?廣度優(yōu)先搜索
????bfs(v){
????????let?queue?=?[];
????????this.marked[v]?=?true;
????????queue.push(v);?//?添加到隊(duì)尾
????????while(queue.length?>?0){
????????????let?v?=?queue.shift();?//?從對(duì)首移除
????????????if(v?!=?undefined){
????????????????console.log("visited?vertex:?"?+?v);
????????????}
????????????this.adj[v].forEach(w?=>?{
????????????????if(!this.marked[w]){
????????????????????this.marked[w]?=?true;
????????????????????queue.push(w);
????????????????}
????????????})
????????}
????}
}

對(duì)于搜索圖,在上面我們介紹了深度優(yōu)先搜索 - DFS(Depth First Search)和廣度優(yōu)先搜索 - BFS(Breadth First Search),結(jié)合下面的圖再回頭看下上面的代碼,你會(huì)更加容易理解這兩種搜索圖的方式。

graph_search

推薦閱讀

拜托,面試官別問我「布隆」了

數(shù)據(jù)結(jié)構(gòu)與算法: 三十張圖弄懂「圖的兩種遍歷方式」

昨天,終于拿到了騰訊 offer

幾道和「二叉樹」有關(guān)的算法面試題

幾道和散列(哈希)表有關(guān)的面試題

一道看完答案你會(huì)覺得很沙雕的「動(dòng)態(tài)規(guī)劃算法題」

幾道和「堆棧、隊(duì)列」有關(guān)的面試算法題

鏈表算法面試問題?看我就夠了!

歡迎關(guān)注

總結(jié)

以上是生活随笔為你收集整理的判断数组中某个元素除自身外是否和其他数据不同_18 张图带你彻底认识这些数据结构...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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