判断数组中某个元素除自身外是否和其他数据不同_18 张图带你彻底认识这些数据结构...
作者 | 嘉明
來源 |?https://github.com/reng99/blogs
數據結構是計算機存儲、組織數據的方式。數據結構是指相互直接存在一種或多種特殊關系的數據元素的集合。通常情況下,精心選擇數據結構可以帶來更高的運行或者存儲效率。作為一名程序猿,更需要了解下數據結構。
講到數據結構,我們都會談到線性結構和非線性結構。
1.線性結構是一個有序數據元素的集合。它應該滿足下面的特征:
集合中必存在唯一的一個“第一個元素”
集合中必存在唯一的一個“最后的元素”
除最后一元素之外,其它數據元素均有唯一的“后繼”
除第一個元素之外,其它數據元素均有唯一的“前驅”
按照百度百科的定義,我們知道符合條件的數據結構就有棧、隊列和其它。
2.非線性結構其邏輯特征是一個節點元素可以有多個直接前驅或多個直接后繼。
那么,符合條件的數據結構就有圖、樹和其它。
嗯~了解一下就行。我們進入正題:
數組
數組是一種線性結構,以十二生肖(鼠、牛、虎、兔、龍、蛇、馬、羊、猴、雞、狗、豬)排序為例:
array_demo我們來創建一個數組并打印出結果就一目了然了:
let?arr?=?['鼠',?'牛',?'虎',?'兔',?'龍',?'蛇',?'馬',?'羊',?'猴',?'雞',?'狗',?'豬'];arr.forEach((item,?index)?=>?{
????console.log(`[?${index}?]?=>?${item}`);
});
//?[?0?]?=>?鼠
//?[?1?]?=>?牛
//?[?2?]?=>?虎
//?[?3?]?=>?兔
//?[?4?]?=>?龍
//?[?5?]?=>?蛇
//?[?6?]?=>?馬
//?[?7?]?=>?羊
//?[?8?]?=>?猴
//?[?9?]?=>?雞
//?[?10?]?=>?狗
//?[?11?]?=>?豬
棧
棧是一種后進先出(LIFO)線性表,是一種基于數組的數據結構。(ps:其實后面講到的數據結構或多或少有數組的影子)
LIFO(Last In First Out)表示后進先出,后進來的元素第一個彈出棧空間。類似于自動餐托盤,最后放上去的托盤,往往先被拿出來使用。
僅允許在表的一端進行插入和移除元素。這一端被稱為棧頂,相對地,把另一端稱為棧底。如下圖的標識。
向一個棧插入新元素稱作進棧、入棧或壓棧,這是將新元素放在棧頂元素上面,使之成為新的棧頂元素。
從一個棧刪除元素又稱為出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。
我們代碼寫下,熟悉下棧:
class?Stack?{????constructor(){
????????this.items?=?[];
????}
????//?入棧操作
????push(element?=?''){
????????if(!element)?return;
????????this.items.push(element);
????????return?this;
????}
????//?出棧操作
????pop(){
????????this.items.pop();
????????return?this;
????}
????//?對棧一瞥,理論上只能看到棧頂或者說即將處理的元素
????peek(){
????????return?this.items[this.size()?-?1];
????}
????//?打印棧數據
????print(){
????????return?this.items.join('?');
????}
????//?棧是否為空
????isEmpty(){
????????return?this.items.length?==?0;
????}
????//?返回棧的元素個數
????size(){
????????return?this.items.length;
????}
}
?? 注意:棧這里的push和pop方法要和數組方法的push和pop方法區分下。
隊列
隊列是一種先進先出(FIFO)受限的線性表。受限體現在于其允許在表的前端(front)進行刪除操作,在表的末尾(rear)進行插入【優先隊列這些排除在外】操作。
queue_demo代碼走一遍:
class?Queue?{????constructor(){
????????this.items?=?[];
????}
????//?入隊操作
????enqueue(element?=?''){
????????if(!element)?return;
????????this.items.push(element);
????????return?this;
????}
????//?出隊操作
????dequeue(){
????????this.items.shift();
????????return?this;
????}
????//?查看隊前元素或者說即將處理的元素
????front(){
????????return?this.items[0];
????}
????//?查看隊列是否為空
????isEmpty(){
????????return?this.items.length?==?0;
????}
????//?查看隊列的長度
????len(){
????????return?this.items.length;
????}
????//?打印隊列數據
????print(){
????????return?this.items.join('?');
????}
}
鏈表
在進入正題之前,我們先來聊聊數組的優缺點。
優點:
存儲多個元素,比較常用
訪問便捷,使用下標[index]即可訪問
缺點:
數組的創建通常需要申請一段連續的內存空間,并且大小是固定的(大多數的編程語言數組都是固定的),所以在進行擴容的時候難以掌控。(一般情況下,申請一個更大的數組,會是之前數組的倍數,比如兩倍。然后,再將原數組中的元素復制過去)
插入數據越是靠前,其成本很高,因為需要進行大量元素的位移。
相對數組,鏈表亦可以存儲多個元素,而且存儲的元素在內容中不必是連續的空間;在插入和刪除數據時,時間復雜度可以達到O(1)。在查找元素的時候,還是需要從頭開始遍歷的,比數組在知道下表的情況下要快,但是數組如果不確定下標的話,那就另說了…
我們使用十二生肖來了解下鏈表:
linklist_demo鏈表是由一組節點組成的集合。每個節點都使用一個對象的引用指向它的后繼。如上圖。下面用代碼實現下:
//?鏈表class?Node?{
????constructor(element){
????????this.element?=?element;
????????this.next?=?null;
????}
}
class?LinkedList?{
????constructor(){
????????this.length?=?0;?//?鏈表長度
????????this.head?=?new?Node('head');?//?表頭節點
????}
????/**
?????*?@method?find?查找元素的功能,找不到的情況下直接返回鏈尾節點
?????*?@param?{?String?}?item?要查找的元素
?????*?@return?{?Object?}?返回查找到的節點?
?????*/
????find(item?=?''){
????????let?currNode?=?this.head;
????????while(currNode.element?!=?item?&&?currNode.next){
????????????currNode?=?currNode.next;
????????}
????????return?currNode;
????}
????/**
????*?@method?findPrevious?查找鏈表指定元素的前一個節點
????*?@param?{?String?}?item?指定的元素
????*?@return?{?Object?}?返回查找到的之前元素的前一個節點,找不到節點的話返回鏈尾節點
????*/
????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;
????}
}
字典
字典的主要特點是鍵值一一對應的關系。可以比喻成我們現實學習中查不同語言翻譯的字典。這里字典的鍵(key)理論上是可以使用任意的內容,但還是建議語意化一點,比如下面的十二生肖圖:
dictionary_democlass?Dictionary?{????constructor(){
????????this.items?=?{};
????}
????/**
?????*?@method?set?設置字典的鍵值對
?????*?@param?{?String?}?key?鍵
?????*?@param?{*}?value?值
?????*/
????set(key?=?'',?value?=?''){
????????this.items[key]?=?value;
????????return?this;
????}
????/**
?????*?@method?get?獲取某個值
?????*?@param?{?String?}?key?鍵
?????*/
????get(key?=?''){
????????return?this.has(key)???this.items[key]?:?undefined;
????}
????/**
?????*?@method?has?判斷是否含有某個鍵的值
?????*?@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;
????}
}
集合
集合通常是由一組無序的,不能重復的元素構成。 一些常見的集合操作如圖:
set_demoes6中已經封裝好了可用的Set類。我們手動來寫下相關的邏輯://?集合
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('?');
????}
????//?移除某個元素
????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?數組集合
?????*?@return?{?Object?}?返回并集的對象
?????*/
????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?數組集合
?????*?@return?{?Object?}?返回交集的對象
?????*/
????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?數組集合
?????*?@return?{?Boolean?}?返回真假值
?????*/
????isSubsetOf(set?=?[]){
????????if(this.size()?>?set.size())?return?false;
????????this.items.forEach*(item?=>?{
????????????if(!set.has(item))?return?false;
????????});
????????return?true;
????}
}
散列表/哈希表
散列是一種常用的存儲技術,散列使用的數據結構叫做散列表/哈希表。在散列表上插入、刪除和取用數據都非常快,但是對于查找操作來說卻效率低下,比如查找一組數據中的最大值和最小值。查找的這些操作得求助其它數據結構,比如下面要講的二叉樹。
切入個案例感受下哈希表:
假如一家公司有1000個員工, 現在我們需要將這些員工的信息使用某種數據結構來保存起來。你會采用什么數據結構呢?
方案一:數組
按照順序將所有員工信息依次存入一個長度為1000的數組中。每個員工的信息都保存在該數組的某個位置上。
但是我們要查看某個員工的信息怎么辦呢?一個個查找嗎?不太好找。
數組最大的優勢是什么?通過下標值獲取信息。
所以為了可以通過數組快速定位到某個員工,最好給員工信息中添加一個員工編號,而編號對應的就是員工的下標值。
當查找某個員工信息時,通過員工號可以快速定位到員工的信息位置。
方案二:鏈表
鏈表對應插入和刪除數據有一定的優勢。
但是對于獲取員工的信息,每次都必須從頭遍歷到尾,這種方式顯然不是特別適合我們這里。
最終方案:
這么看最終方案似乎就是數組了,但是數組還是有缺點,什么缺點呢?
假如我們想查看下張三這位員工的信息,但是我們不知道張三的員工編號,怎么辦呢?
當然,我們可以問他的員工編號。但是我們每查找一個員工都是要問一下這個員工的編號嗎?不合適。【那我們還不如直接問他的信息嘞】
能不能有一種辦法,讓張三的名字和他的員工編號產生直接的關系呢?
也就是通過張三這個名字,我們就能獲取到他的索引值,而再通過索引值我們就能獲取張三的信息呢?
這樣的方案已經存在了,就是使用哈希函數,讓某個key的信息和索引值對應起來。
那么散列表的原理和實現又是怎樣的呢,我們來聊聊。
我們的哈希表是基于數組完成的,我們從數組這里切入解析下。數組可以通過下標直接定位到相應的空間,哈希表的做法就是類似的實現。哈希表把key(鍵)通過一個固定的算法函數(此函數稱為哈希函數/散列函數)轉換成一個整型數字,然后就將該數字對數組長度進行取余,取余結果就當作數組的下標,將value(值)存儲在以該數字為下標的數組空間里,而當使用哈希表進行查詢的時候,就是再次使用哈希函數將key轉換為對應的數組下標,并定位到該空間獲取value。
結合下面的代碼,也許你會更容易理解:
//?哈希表class?HashTable?{
????constructor(){
????????this.table?=?new?Array(137);
????}
????/**
?????*?@method?hashFn?哈希函數
?????*?@param?{?String?}?data?傳入的字符串
?????*?@return?{?Number?}?返回取余的數字
?????*/
????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函數等看官感興趣的話自己補充測試啦
}hashtable_demo
針對上面的問題,我們存儲數據的時候,產生沖突的話我們可以像下面這樣解決:
1. 線性探測法
當發生碰撞(沖突)時,線性探測法檢查散列表中的下一個位置【有可能非順序查找位置,不一定是下一個位置】是否為空。如果為空,就將數據存入該位置;如果不為空,則繼續檢查下一個位置,直到找到一個空的位置為止。該技術是基于一個事實:每個散列表都有很多空的單元格,可以使用它們存儲數據。
2. 開鏈法
但是,當發生碰撞時,我們任然希望將key(鍵)存儲到通過哈希函數產生的索引位置上,那么我們可以使用開鏈法。開鏈法是指實現哈希表底層的數組中,每個數組元素又是一個新的數據結構,比如另一個數組(這樣結合起來就是二位數組了),鏈表等,這樣就能存儲多個鍵了。使用這種技術,即使兩個key(鍵)散列后的值相同,依然是被保存在同樣的位置,只不過它們是被保存在另一個數據結構上而已。以另一個數據結構是數組為例,存儲的數據如下:
open_link_method二叉查找樹
樹的定義:
樹(Tree):n(n <= 0)個節點構成的有限集合。
當`n = 0`時,稱為空樹;
對任意一棵空樹`(n > 0)`,它具備以下性質:
樹中有一個稱為根(Root)的特殊節點,用`r(root)`表示;
其余節點可分為`m(m > 0)`個互不相交的有限集`T1,T2,…Tm`,其中每個集合本省又是一棵樹,稱為原來樹的子樹(SubTree)
注意:
子樹之間`不可以相交`;
除了根節點外,每個節點有且僅有一個父節點;
一個`N`個節點的樹有`N-1`條邊。
樹的術語:
節點的度(Degree):節點的子樹個數。
樹的度:樹的所有節點中最大的度數(樹的度通常為節點個數的N-1)。
葉節點(Leaf):度為0的節點(也稱葉子節點)。
父節點(Parent):有子樹的節點是其子樹的父節點。
子節點(Child):若A節點是B節點的父節點,則稱B節點是A節點的子節點。
兄弟節點(Sibling):具有同一個父節點的各節點彼此是兄弟節點。
路徑和路徑長度:從節點n1到nk的路徑為一個節點序列n1,n2,n3,…,nk,ni是ni+1的父節點。路徑所包含邊的個數為路徑長度。
節點的層次(Level):規定根節點在第0層,它的子節點是第1層,子節點的子節點是第2層,以此類推。
樹的深度(Depth):樹中所有節點中的最大層次是這棵樹的深度(因為上面是從第0層開始,深度 = 第最大層數 + 1)
如下圖:
tree_intro二叉樹的定義:
二叉樹可以為空,也就是沒有節點
二叉樹若不為空,則它是由根節點和稱為其左子樹TL和右子樹RT的兩個不相交的二叉樹組成
二叉樹每個節點的子節點不允許超過兩個
二叉樹的五種形態:
空
只有根節點
只有左子樹
只有右子樹
左右子樹均有
對應下圖(從左至右):
five_style_binary_tree我們接下來要講的是二叉查找樹(BST,Binary Search Tree)。二叉查找樹,也稱二叉搜索樹或二叉排序樹,是一種特殊的二叉樹,相對值較小的值保存在左節點中,較大的值保存在右節點中。二叉查找樹特殊的結構使它能夠快速的進行查找、插入和刪除數據。下面我們來實現下:
//?二叉查找樹//?輔助節點類
class?Node?{
????constructor(data,?left,?right){
????????this.data?=?data;
????????this.left?=?left;
????????this.right?=?right;
????}
????//?展示節點信息
????show(){
????????return?this.data;
????}
}
class?BST?{
????constructor(){
????????this.root?=?null;
????}
????//?插入數據
????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;
????}
????//?移除給定值的輔助函數
????removeNode(node,?data){
????????if(node?==?null){
????????????return?null;
????????}
????????if(data?==?node.data){
????????????//?葉子節點
????????????if(node.left?==?null?&&?node.right?==?null){
????????????????return?null;?//?此節點置空
????????????}
????????????//?沒有左子樹
????????????if(node.left?==?null){
????????????????return?node.right;
????????????}
????????????//?沒有右子樹
????????????if(node.right?==?null){
????????????????return?node.left;
????????????}
????????????//?有兩個子節點的情況
????????????let?tempNode?=?this.getSmallest(node.right);?//?獲取右子樹
????????????node.data?=?tempNode.data;?//?將其右子樹的最小值賦值給刪除的那個節點值
????????????node.right?=?this.removeNode(node.right,?tempNode.data);?//?刪除指定節點的下的最小值,也就是置其為空
????????????return?node;
????????}else?if(data?????????????node.left?=?this.removeNode(node.left,?data);
????????????return?node;
????????}else{
????????????node.right?=?this.removeNode(node.right,?data);
????????????return?node;
????????}
????}
????//?獲取給定節點下的二叉樹最小值的輔助函數
????getSmallest(node){
????????if(node.left?==?null){
????????????return?node;
????????}else{
????????????return?this.getSmallest(node.left);
????????}
????}
}
看了上面的代碼之后,你是否有些懵圈呢?我們借助幾張圖來了解下,或許你就豁然開朗了。
在遍歷的時候,我們分為三種遍歷方法--先序遍歷,中序遍歷和后序遍歷:
travel_tree刪除節點是一個比較復雜的操作,考慮的情況比較多:
該節點沒有葉子節點的時候,直接將該節點置空;
該節點只有左子樹,直接將該節點賦予左子樹
該節點只有右子樹,直接將該節點賦予右子樹
該節點左右子樹都有,有兩種方法可以處理
方案一:從待刪除節點的左子樹找節點值最大的節點A,替換待刪除節點值,并刪除節點A
方案二:從待刪除節點的右子樹找節點值最小的節點A,替換待刪除節點值,并刪除節點A【?上面的示例代碼中就是這種方案】
刪除兩個節點的圖解如下:
remove_tree_node圖
圖由邊的集合及頂點的集合組成。
我們來了解下圖的相關術語:
頂點:圖中的一個節點。
邊:表示頂點和頂點之間的連線。
相鄰頂點:由一條邊連接在一起的頂點稱為相鄰頂點。
度:一個頂點的度是相鄰頂點的數量。比如0頂點和其它兩個頂點相連,0頂點的度就是2
路徑:路徑是頂點
v1,v2...,vn的一個連續序列。
簡單路徑:簡單路徑要求不包含重復的頂點。
回路:第一個頂點和最后一個頂點相同的路徑稱為回路。
有向圖和無向圖
有向圖表示圖中的邊是有方向的。
無向圖表示圖中的邊是無方向的。
帶權圖和無權圖
帶權圖表示圖中的邊有權重。
無權圖表示圖中的邊無權重。
如下圖:
graph_concept_intro圖可以用于現實中的很多系統建模,比如:
對交通流量建模
頂點可以表示街道的十字路口, 邊可以表示街道.
加權的邊可以表示限速或者車道的數量或者街道的距離.
建模人員可以用這個系統來判定最佳路線以及最可能堵車的街道.
圖既然這么方便,我們來用代碼實現下:
//?圖class?Graph{
????constructor(v){
????????this.vertices?=?v;?//?頂點個數
????????this.edges?=?0;?//?邊的個數
????????this.adj?=?[];?//?鄰接表或鄰接表數組
????????this.marked?=?[];?//?存儲頂點是否被訪問過的標識
????????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]);
????????????????}
????????????}
????????}
????}
????//?深度優先搜索
????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);
????????????}
????????})
????}
????//?廣度優先搜索
????bfs(v){
????????let?queue?=?[];
????????this.marked[v]?=?true;
????????queue.push(v);?//?添加到隊尾
????????while(queue.length?>?0){
????????????let?v?=?queue.shift();?//?從對首移除
????????????if(v?!=?undefined){
????????????????console.log("visited?vertex:?"?+?v);
????????????}
????????????this.adj[v].forEach(w?=>?{
????????????????if(!this.marked[w]){
????????????????????this.marked[w]?=?true;
????????????????????queue.push(w);
????????????????}
????????????})
????????}
????}
}
對于搜索圖,在上面我們介紹了深度優先搜索 - DFS(Depth First Search)和廣度優先搜索 - BFS(Breadth First Search),結合下面的圖再回頭看下上面的代碼,你會更加容易理解這兩種搜索圖的方式。
graph_search推薦閱讀
拜托,面試官別問我「布隆」了
數據結構與算法: 三十張圖弄懂「圖的兩種遍歷方式」
昨天,終于拿到了騰訊 offer
幾道和「二叉樹」有關的算法面試題
幾道和散列(哈希)表有關的面試題
一道看完答案你會覺得很沙雕的「動態規劃算法題」
幾道和「堆棧、隊列」有關的面試算法題
鏈表算法面試問題?看我就夠了!
歡迎關注
戳總結
以上是生活随笔為你收集整理的判断数组中某个元素除自身外是否和其他数据不同_18 张图带你彻底认识这些数据结构...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HOJ 2576 HOJ 2577
- 下一篇: JavaScript|拖拽|仿Andro