口香糖變身藝術品 英藝術家巧手改造垃圾

摘錄自2020年2月24日公視新聞網報導

英國藝術家和美國設計師用他們的巧思,讓街邊亂吐的口香糖或是廢棄布料大變身。

紡織和時裝業是地球上第二大的淡水污染源,美國環保署統計,全美紡織品垃圾2017年統計有1700萬噸,其中1100萬噸送到垃圾場掩埋,只有約260萬噸被回收。

紐約設計師丹尼爾使用廢棄布料創造流行服飾,希望業界能做出反思。2015年開了自己的服飾店,店名叫零廢棄,他的設計受到年輕人喜愛,也在博物館展出,他甚至希望同業複製模仿他的設計,鼓吹透過處理舊服飾和廢棄碎布,創造出環保永續的時尚理念。

而現年57歲的英國人威爾森,原本是一名木雕師,15年前在街上看到黏在地上的口香糖,決定進行大改造。威爾森解釋:「我使用壓克力瓷漆來作畫,然後將口香糖加熱,等它乾後我再噴上噴漆。」一個作品約花費2小時完成,他的作品目前遍布歐洲各地,估計有數千個,他說這就像是每個城市的隱藏藝術,希望人們在欣賞藝術的同時,也不要再亂丟垃圾了。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

樂視與阿斯頓•馬丁將成立電動汽車合資公司 2018年推出量產版RapidE

樂視與阿斯頓•馬丁將成立電動汽車合資公司,該合資公司中雙方占股比為50:50,未來合作合資公司的經營內容將電動汽車的研發、生產、製造與銷售等。

合作範圍將涵蓋阿斯頓•馬丁品牌、樂視超級汽車以及樂視的合作夥伴Faraday Future的電動車型。同時,樂視還將有望利用其電商平臺以及豐富的互聯網傳播及行銷資源,幫助阿斯頓•馬丁的車型提升銷量。

首款合作車型為RapidE

據雙方透露,阿斯頓•馬丁RapidE將成為雙方合力打造的首款車型。其中,阿斯頓•馬丁負責提供RapidE的整車技術,而樂視則將提供動力總成和車聯網技術。

RapidE是阿斯頓•馬丁推出的首款電動超跑概念車。該款概念車亮相以來,阿斯頓•馬丁和樂視一直在密切合作以推進該車的量產,包括尋求電池系統和動力總成的解決方案,力爭於2018年推出量產版的RapidE。

據悉,RapidE將會在阿斯頓•馬丁的總部所在地英國華威郡蓋登(Gaydon)進行生產,同時該車型會從全球頂尖的供應商那裡獲得先進的技術解決方案,這其中也包括來自英國和中國的供應商企業。

阿斯頓•馬丁為樂視超級汽車代工、提供研發支援

根據雙方的合作計畫,阿斯頓•馬丁將成為未來樂視超級汽車的代工商,並作為工程服務提供者提供車身和整車技術。同時,阿斯頓•馬丁還將為樂視提供研發支援,利用其在高端車型領域的經驗幫助樂視打造超級汽車。

 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

補償計畫,美國要求福斯在美生產電動車

福斯汽車先前傳出廢氣排放測試造假的醜聞,引發喧然大波。為進行補償,美國環境保護署(EPA)要求福斯汽車在美國本土生產電動車,同時建置電動車充電網絡。

路透社引述德國媒體《Welt am Sonntag》表示,EPA正與福斯汽車洽談福斯60萬輛柴油汽車排氣檢測造假之事要如何補償。這些排氣造假汽車所排放的廢氣,比美國法定廢氣排放標準高出40倍。

文章指出,EPA要求福斯汽車在美國田納西州的Chattanooga工廠就地生產電動車,同時須在美國建立電動汽車衝電網絡。不過,文章並未明確指出EPA是要求福斯生產福斯在美既有的電動車與油電混和車車款,還是要研發、生產新的車款。

此外,福斯表示仍在持續與EPA協商補償事宜中。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

中國鬆口承認蝗蟲可能從印巴入侵 急發防蝗緊急通知

摘錄自2020年3月3日中央廣播電台報導

源自東非的蝗災肆虐南亞逐漸向中國逼近,早先中國農業農村部與中國科學院院士康樂才對外表示這波蝗災影響中國可能性不大,不過中國國家林草局2日卻發出緊急通知,明白指出沙漠蝗蟲可能從巴基斯坦和印度沿青藏高原河谷地帶入侵西藏、雲南、新疆等地,坦言存在造成危害的風險,要求確實做好防控。

武漢肺炎(COVID-19)疫情還在中國持續流竄,但源自於非洲的蝗災卻已經蔓延到巴基斯坦、印度等地,早先聯合國糧農組織(Food and Agriculture Organization, FAO)透露,這波蝗災有可能延續至2020年6月,屆時蝗群規模可能比目前大出500倍,呼籲全球戒備,但中國農業單位認為,中國蝗蟲監測預警和防治能力提升,防蝗藥械儲備充足,大面積爆發蝗災的風險很低。不過中國國家林草局2日則是發出一份緊急通知,公開呼籲民眾做好蝗災的防範。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

奔馳下一代汽車將棄用特斯拉電動傳動系統 採用自家技術

戴姆勒電動汽車主管哈拉爾德•克勒格爾(Harald Kroeger)在接受德國媒體採訪時說,為了開發自有傳動系統和電池技術,奔馳已經投資了5.5億美元,下一代B級Electric Drive電動汽車不會使用特斯拉傳動系統。

奔馳這樣做合情合理。2014年年末時,奔馳基本上已經清空了特斯拉持股。在2014年的年報中特斯拉也說B級車研發合作已經基本完成。到了2015年年底,特斯拉已經銷售10萬輛電動汽車。以前特斯拉是矽谷新崛起的明星企業,一些大型汽車公司有興趣與它合作,現在汽車公司已經將特斯拉當成了競爭對手。比如豐田,在特斯拉成立之初,豐田也曾參與投資,特斯拉還為豐田電動版RAV4提供傳動系統。

那時的特斯拉還很年輕,它還沒有推出Model S轎車,如果能與大型汽車商合作對公司的發展無疑是有利的。特斯拉需要增加營收,只有有了營收,公司才有可能轉變為主流汽車製造商。另外,在公司沒有上市之前,它需要吸引早期投資者來支援自己。今天的特斯拉已經完全不同了,Model S大獲成功,Model X剛剛開始向用戶交貨。本月晚些時候Model 3也將上市,這是一款低價電動汽車。可以說現在的特斯拉已經是一家規模雖小但是卻很成熟的電動汽車製造商了。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

不止面試—jvm類加載面試題詳解

面試題

帶着問題學習是最高效的,本次我們將嘗試回答以下問題:

  1. 什麼是類的加載?
  2. 哪些情況會觸發類的加載?
  3. 講一下JVM加載一個類的過程
  4. 什麼時候會為變量分配內存?
  5. JVM的類加載機制是什麼?
  6. 雙親委派機制可以打破嗎?為什麼

答案放在文章的最後,來不及看原理也可以直接跳到最後直接看答案。

深入原理

類的生命周期

類的生命周期相信大家已經耳熟能詳,就像下面這樣:

不過這東西總是背了就忘,忘了又背,就像馬什麼梅一樣,對吧?

其實理解之後,基本上就不會再忘了。

加載

加載主要做三件事:

  1. 找到類文件(通過類的全限定名來獲取定義此類的二進制字節流)
  2. 放入方法區(將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構)
  3. 開個入口(生成一個代表此類的java.lang.Class對象,作為訪問方法區這些數據結構的入口)

總的來講,這一步就是通過類加載器把類讀入內存。需要注意的是,第三步雖然生成了對象,但並不在堆里,而是在方法區里。

連接

連接分為三步,一般面試都比較喜歡問準備這一步。

校驗

顧名思義,檢查Class文件的字節流中包含的信息是否符合當前虛擬機的要求。

準備

這一步中將為靜態變量和靜態常量分配內存,並賦值。

需要注意的是,靜態變量只會給默認值。比如下面這個:

public static int value = 123;

此時賦給value的值是0,不是123。

靜態常量(static final修飾的)則會直接賦值。比如下面這個:

public static final int value = 123;

此時賦給value的值是123。

解析

解析階段就是jvm將常量池的符號引用替換為直接引用。

恩……啥是常量池?啥是符號引用?啥是直接引用?

常量池我們放在jvm內存結構里說。先來說下什麼是符號引用和直接引用。

符號引用和直接引用

假設有一個Worker類,包含了一個Car類的run()方法,像下面這樣:

class Worker{
    ......
    public void gotoWork(){
        car.run(); //這段代碼在Worker類中的二進製表示為符號引用        
    }
    ......
}

在解析階段之前,Worker類並不知道car.run()這個方法內存的什麼地方,於是只能用一個字符串來表示這個方法。該字符串包含了足夠的信息,比如類的信息,方法名,方法參數等,以供實際使用時可以找到相應的位置。

這個字符串就被稱為符號引用

在解析階段,jvm根據字符串的內容找到內存區域中相應的地址,然後把符號引用替換成直接指向目標的指針、句柄、偏移量等,這之後就可以直接使用了。

這些直接指向目標的指針、句柄、偏移量就被成為直接引用

初始化

類的初始化的主要工作是為靜態變量賦程序設定的初值。

還記得上面的靜態變量嗎:

public static int value = 123;

經過這一步,value的值終於是123了。

總結如下圖:

類初始化的條件

Java虛擬機規範中嚴格規定了有且只有五種情況必須對類進行初始化:

  1. 使用new字節碼指令創建類的實例,或者使用getstatic、putstatic讀取或設置一個靜態字段的值(放入常量池中的常量除外),或者調用一個靜態方法的時候,對應類必須進行過初始化。
  2. 通過java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則要首先進行初始化。
  3. 當初始化一個類的時候,如果發現其父類沒有進行過初始化,則首先觸發父類初始化。
  4. 當虛擬機啟動時,用戶需要指定一個主類(包含main()方法的類),虛擬機會首先初始化這個類。
  5. 使用jdk1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有進行初始化,則需要先觸發其初始化。

除了以上這五種情況,其他任何情況都不會觸發類的初始化。

比如下面這幾種情況就不會觸發類初始化:

  1. 通過子類調用父類的靜態字段。此時父類符合情況一,而子類不符合任何情況。所以只有父類被初始化。
  2. 通過數組來引用類,不會觸發類的初始化。因為new的是數組,而不是類。
  3. 調用類的靜態常量不會觸發類的初始化,因為靜態常量在編譯階段就會被存入調用類的常量池中,不會引用到定義常量的類。

類加載機制

類加載器

在上面咱們曾經說到,加載階段需要“通過一個類的全限定名來獲取描述此類的二進制字節流”。這件事情就是類加載器在做。

jvm自帶三種類加載器,分別是:

  1. 啟動類加載器。
  2. 擴展類加載器。
  3. 應用程序類加載器

他們的繼承關係如下圖:

雙親委派

雙親委派機制工作過程如下:

  1. 當前ClassLoader首先從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。每個類加載器都有自己的加載緩存,當一個類被加載了以後就會放入緩存,等下次加載的時候就可以直接返回了。

  2.  當前classLoader的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用同樣的策略,首先查看自己的緩存,然後委託父類的父類去加載,一直到bootstrp ClassLoader.

  3.  當所有的父類加載器都沒有加載的時候,再由當前的類加載器加載,並將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。

為啥要搞這麼複雜?自己處理不好嗎?

雙親委派的優點如下:

  1. 避免重複加載。當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。
  2. 為了安全。避免核心類,比如String被替換。

打破雙親委派

“雙親委派”機制只是Java推薦的機制,並不是強制的機制。

比如JDBC就打破了雙親委派機制。它通過Thread.currentThread().getContextClassLoader()得到線程上下文加載器來加載Driver實現類,從而打破了雙親委派機制。

至於為什麼,以後再說吧。

答案

現在,我們可以回答文章開頭提出的問題了。盡量在理解的基礎上回答,不需要死記硬背。

  1. 什麼是類的加載?

    JVM把通過類名獲得類的二進制流之後,把類放入方法區,並創建入口對象的過程被稱為類的加載。經過加載,類就被放到內存里了。

  2. 哪些情況會觸發類的初始化?

    類在5種情況下會被初始化:

    第一,假如這個類是入口類,他會被初始化。

    第二,使用new創建對象,或者調用類的靜態變量,類會被初始化。不過靜態常量不算。

    第三,通過反射獲取類,類會被初始化

    第四,如果子類被初始化,他的父類也會被初始化。

    第五,使用jdk1.7的動態語言支持時,調用到靜態句柄,也會被初始化。

  3. 講一下JVM加載一個類的過程

    同問題1。不過這裏也可以問下面試官是不是想問類的生命周期。如果是問類的生命周期,可以回答有”加載、連接、初始化、使用、卸載“五個階段,連接又可以分為”校驗、準備、解析“三個階段。

  4. 什麼時候會為變量分配內存?

    在準備階段為靜態變量分配內存。

  5. JVM的類加載機制是什麼?

    雙親委派機制,類加載器會先讓自己的父類來加載,父類無法加載的話,才會自己來加載。

  6. 雙親委派機制可以打破嗎?為什麼

    可以打破,比如JDBC使用線程上下文加載器打破了雙親委派機制。原因是JDBC只提供了接口,並沒有提供實現。這個問題可以再看下引用文獻的內容。

引用文獻

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

斯柯達混動/純電動汽車未來幾年將相繼上市

近日,據海外媒體報導,斯柯達在未來幾年計畫推出一系列新能源車,其中包括或將於2019年上市的插電式混動車型以及最早2020年才能推出的純電動版車型。

斯柯達未來的純電動車型就將基於大眾MEB電動車模組化平臺進行打造,目前在考慮推出晶銳和明銳的純電動版車型,不過即使推出的話,最早也要等到2020年,不知道那時候某些城市購買純電動車搖號的話會不會很費勁了。

在推出純電動車之前,斯柯達計畫先推出插電式混動車型,首先考慮的是旗艦車型速派,最早能在2019年正式推出。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

心裏有點樹

why 樹形結構

  • 順序存儲

順序存儲的特點是各個存儲單位在邏輯和物理內存上都是相鄰的,典型的就是代表就是數組,物理地址相鄰因此我們可以通過下標很快的檢索出一個元素

我們想往數組中添加一個元素最快的方式就是往它的尾部添加.如果往頭部添加元素的話,效率就很低,因為需要將從第一個元素開始依次往後移動一位,這樣就能空出第一位的元素,然後才能我們指定的數據插入到第一個的位置上

  • 鏈式存儲

鏈式存儲的特點是,各個節點之間邏輯是相鄰的,但是物理存儲上不相鄰,每一個節點都存放一個指針或者是引用用來指向它的前驅或者後繼節點, 因此我們想插入或者刪除一個元素時速度就會很塊,只需要變動一下指針的指向就行

但是對鏈表來說查找是很慢的, 因此對任意一個節點來書,他只知道自己的下一個節點或者是上一個節點在哪裡,再多的他就不不知道了,因此需要從頭結點開始遍歷…

樹型存儲結構有很多種,比如什麼二叉樹,滿二叉樹,紅黑樹,B樹等, 對於樹形結構來說,它會相對中和鏈式存儲結構和順序存儲結構的優缺點 (其中二叉排序樹最能直接的體會出樹中和鏈式存儲和線性存儲的特性,可以通過右邊的導航先去看看二叉排序樹)

樹的概述

如上圖是一個二叉樹, 當然樹還能有三叉,四叉等等…

  • 根節點: 最頂上的節點 即A
    層: 根節點在第一層 BE在第二層
    高度: 最大的層數
    森林: 多個樹的組合
    權: 節點上的值 如根節點的權是 A
    恭弘=叶 恭弘子節點: 下層上的節點是上一層的恭弘=叶 恭弘子節點
    雙親節點: 上層的節點是下層的節點的雙親節點(單個節點又是爸又是媽)
    路徑: 找到C的路徑是 A-B-C
    度: 就是直接子節點的個數

普通二叉樹

  • 什麼是二叉樹?

顧名思義就是度最大為2的樹就是二叉樹.而且對二叉樹來說,是嚴格區分左子樹和右子樹的,看上圖,雖然兩個樹的根節點都是1,但是他們的左右子樹不同,因此他們並不是相同的樹

  • 什麼是滿二叉樹?

像上圖這樣,所有的恭弘=叶 恭弘子節點都在最後一層,所有的且除了最後一層其他層的節點都有兩個子節點

二叉樹的全部節點計算公式是 2^n-1 , n是層數

  • 什麼是完全二叉樹?

像上圖這樣, 所有的恭弘=叶 恭弘子點都在最後一層或者是倒數第二層, 並且從左往右數是連續的

java&二叉樹

  • 封裝二叉樹節點
public class TreeNode {
    // 權
    private int value;
    // 左節點
    private TreeNode leftNode;
    // 右節點
    private TreeNode rightNode;
}
  • 封裝二叉樹
public class BinaryTree {
    TreeNode root;

    public void setRoot(TreeNode root) {
        this.root = root;
    }

    public TreeNode getRoot() {
        return this.getRoot();
    }
}

遍歷

像這樣一顆二叉樹,通過不同的書序遍歷會得到不同的結果

前中后的順序說的是root節點的順序,前序的話就是先遍歷父節點, 中序就是左父右 後續就是左右父

  • 前序遍歷
 public void frontShow() {
        System.out.println(this.value);
        if (leftNode != null)
            leftNode.frontShow();

        if (rightNode != null)
            rightNode.frontShow();
    }
  • 中序遍歷
    public void middleShow() {
        if (leftNode != null)
            leftNode.middleShow();

        System.out.println(value);

        if (rightNode != null)
            rightNode.middleShow();
    }
  • 後續遍歷
    public void backShow() {
        if (leftNode != null)
            leftNode.backShow();

        if (rightNode != null)
            rightNode.backShow();

        System.out.println(value);
    }

查找

其實有了上面三種遍歷的方式, 查找自然存在三種, 一遍遍歷一遍查找

    public TreeNode frontSeach(int num) {
        TreeNode node = null;
        // 當前節點不為空,返回當前節點
        if (num == this.value) {
            return this;
        } else {
            // 查找左節點
            if (leftNode != null) {
                node = leftNode.frontSeach(num);
            }
            if (node != null)
                return node;
            // 查找右節點
            if (rightNode != null)
                node = rightNode.frontSeach(num);
        }
        return node;
    }

刪除節點

刪除節點也是, 不考慮特別複雜的情況, 刪除節點就有兩種情況, 第一種要刪除的節點就是根節點, 那麼讓根節點=null就ok, 第二種情況要刪除的節點不是根節點,就處理它的左右節點, 左右節點還不是需要刪除的元素的話那麼就得遞歸循環這個過程

   // 先判斷是否是根節點,在調用如下方法
    
   public void deleteNode(int i) {
        TreeNode parent = this;
        // 處理左樹
        if (parent.leftNode!=null&&parent.leftNode.value==i){
            parent.leftNode=null;
            return;
        }
        // 處理左樹
        if (parent.rightNode!=null&&parent.rightNode.value==i){
            parent.rightNode=null;
            return;
        }
        // 遞歸-重置父節點
        parent=leftNode;
        if (parent!=null)
            parent.deleteNode(i);
        // 遞歸-重置父節點
        parent=rightNode;
        if (parent!=null)
            parent.deleteNode(i);

    }

順序存儲二叉樹

文章一開始剛說了, 順序存儲的數據結構的典型代表就是數組, 就像這樣

[1,2,3,4,5,6,7]

什麼是順序存儲的二叉樹呢? 其實就是將上面的數組看成了一顆樹,就像下圖這樣

數組轉換成二叉樹是有規律的, 這個規律就體現在他們的 下標的關聯上, 比如我們想找2節點的左子節點的下標就是 2*n -1 = 3 , 於是我們從數組中下標為3的位置取出4來

  • 第n個元素的左子節點是 2n-1
  • 第n個元素的右子節點是 2n-2
  • 第n個元素的父節點是 (n-1)/2

  • 遍歷順序存儲的二叉樹

    public void frontShow(int start){
        if (data==null||data.length==0){
            return;
        }
        // 遍歷當前節點
        System.out.println(data[start]);
        // 遍歷左樹
        if (2*start+1<data.length)
            frontShow(2*start+1);
        // 遍歷右樹
        if (2*start+2<data.length)
            frontShow(2*start+2);
    }

線索二叉樹

假設我們有下面的二叉樹, 然後我們可以使用中序遍歷它, 中序遍歷的結果是 4,2,5,1,3,6 但是很快我們就發現了兩個問題, 啥問題呢?

  • 問題1: 雖然可以正確的遍歷出 4,2,5,1,3,6 , 但是當我們遍歷到2時, 我們是不知道2的前一個是誰的,(哪怕我們剛才遍歷到了它的前一個節點就是4)

  • 問題2: node4,5,6,3的左右節點的引用存在空閑的情況

針對這個現狀做出了改進就是線索化二叉樹, 它可以充分利用各個節點中剩餘的node這個現狀…線索化后如下圖

  • 如果這個節點的右節點為空,我們就讓它讓它指向自己的後繼節點, 例如上圖的紅線
  • 如何節點的左節點為空, 就讓這個空閑的節點指向它的前驅節點,例如上圖的藍色線

這樣的話, 就實現了任意獲取出一個節點我們都能直接的得知它的前驅節點后後繼節點到底是誰

java&中序化二叉樹;

思路: 按照原來中序遍歷樹的思路,對樹進行中序遍歷,一路遞歸到4這個節點, 檢查到它的左節點為空,就將他的左節點指向它的前驅節點, 可是4本來就是最前的節點,故4這個節點的左節點自然指向了null

然後看它的右節點也為空,於是將他的右節點指向它的後繼節點, 可是這時依然沒獲取到2節點的引用怎麼辦呢? 於是先找個變量將4節點臨時存起來, 再往後遞歸,等遞歸到2節點時,取出臨時變量的4節點, 4節點.setRightNode(2節點)

然後重複這個過程

    // 臨時保存上一個節點
    private TreeNode preNode;

    // 中序線索化二叉樹
    void threadNode(TreeNode node) {
        if (node == null)
            return;

        // 處理左邊
        threadNode(node.getLeftNode());

        // 左節點為空,說明沒有左子節點, 讓這個空出的左節點指向它的上一個節點
        if (node.getLeftNode() == null) {
            // 指向上一個節點
            node.setLeftNode(preNode);
            // 標識節點的類型
            node.setLeftType(1);
        }

        // 處理前驅節點的右指針
        // 比如現在遍歷到了1, 1的上一個節點是5, 5的右邊空着了, 於是讓5的有節點指向1
        if (preNode != null && preNode.getRightNode() == null) {
            preNode.setRightNode(node);
            preNode.setRightType(1);
        }

        // 每次遞歸調用一次這個方法就更新前驅節點
        preNode = node;
        // 處理右邊
        threadNode(node.getRightNode());
    }

遍歷二叉樹

    public void threadIterator() {
        TreeNode node = root;
        while (node != null) {
            // 循環找
            while (node.getLeftType() == 0)
                node = node.getLeftNode();
            // 打印當前節點
            System.out.println(node.getValue());
            // 如果當前的節點的右type=1說明它有指針指向自己的前一個節點
            // 比如現在位置是4, 通過下面的代碼可以讓node=2
            while (node.getRightType() == 1) {
                node = node.getRightNode();
                System.out.println(node.getValue());
            }

            // 替換遍歷的節點, 可以讓 node從2指向 5, 或者從3指向1
            node = node.getRightNode();
        }

    }

赫夫曼樹(最優二叉樹)

定義: 什麼是赫夫曼樹

赫夫曼樹又稱為最優二叉樹

定義: 在N個帶權的恭弘=叶 恭弘子節點的所組成的所有二叉樹中,如果你能找出那個帶權路徑最小的二叉樹,他就是赫夫曼樹

一說起來赫夫曼樹,其實我們可以只關心它的恭弘=叶 恭弘子節點, 權, 路徑這三個要素

  • 什麼是恭弘=叶 恭弘子節點的帶權路徑?

所謂權,其實就是節點的值, 比如上圖中node4的權是8 , node5的權是6 ,node3的權是1, 而且我們只關心恭弘=叶 恭弘子節點的權

啥是帶權路徑呢? 比如上圖中 node4的帶權路徑是 1-2-4

  • 樹的帶權路徑長度(weight path length) 簡稱 WPL

其實就是這個樹所有的恭弘=叶 恭弘子節點的帶權路徑長度之和,

計算左樹的WPL =2*8+2*6+1*1 = 29

計算左樹的WPL =2*1+2*6+1*8 = 22

總結: 權值越大的節點,離根節點越近的節點是最優二叉樹

### 實戰: 將數組轉換為赫夫曼樹

  • 思路:

假設我們現在已經有了數組 [3,5,7,8,11,14,23,29], 如何將這個數組轉換成赫夫曼樹呢?

取出這裏最小的node3 和 倒數第二小的node5 ,構建成新的樹, 新樹的根節點是 node3,5的權值之和, 將構建完成的樹放回到原數組中

重複這個過程, 將最小的node7,node8取出,構建新樹, 同樣新樹的權重就是 node7,8的權重之和, 再將構建完成的樹放回到原數組中

如此往複,最終得到的樹就是huffman樹

  • java實現:

封裝TreeNode, 看上面的過程可以看到,需要比較權重的大小,因此重寫它的compareTo方法

public class TreeNode implements Comparable{
    // 權
    private int value;
    private TreeNode leftNode;
    private TreeNode rightNode;

    @Override
    public int compareTo(Object o) {
        TreeNode node = (TreeNode) o;
        return this.value-node.value;
    }

構建赫夫曼樹, 思路就是上圖的過程, 將數組中的各個元素轉換成Node. 然後存放在List容器中,每輪構建新樹時需要排序, 當集合中僅剩下一個節點,也就是根節點時完成樹的構建

    // 創建赫夫曼樹
    private static TreeNode buildHuffmanTree(int[] arr) {
        // 創建一個集合,存放將arr轉換成的二叉樹
        ArrayList<TreeNode> list = new ArrayList<>();
        for (int i : arr) {
            list.add(new TreeNode(i));
        }
        // 開始循環, 當集合中只剩下一棵樹時
        while (list.size() > 1) {
            // 排序
            Collections.sort(list);
            // 取出權值最小的數
            TreeNode leftNode = list.get(list.size() - 1);
            // 取出權值次要小的數
            TreeNode rightNode = list.get(list.size() - 2);
            // 移除取出的兩棵樹
            list.remove(leftNode);
            list.remove(rightNode);

            // 創建新的樹根節點
            TreeNode parentNode = new TreeNode(leftNode.getValue() + rightNode.getValue(), leftNode, rightNode);
            // 將新樹放到原樹的集合中
            list.add(parentNode);
        }
        return list.get(0);
    }

實戰: 赫夫曼樹與數據壓縮

通過上面的介紹我們能直觀的看出來,赫夫曼樹很顯眼的特徵就是它是各個節點能組成的樹中,那顆WPL,帶權路徑長度最短的樹, 利用這條性質常用在數據壓縮領域, 即我們將現有的數據構建成一個赫夫曼樹, 其中出現次數越多的字符,就越靠近根節點, 經過這樣的處理, 就能用最短的方式表示出原有字符

假設我們有這條消息can you can a can as a canner can a can.

數據對計算機來說不過是0-1這樣的数字, 我們看看將上面的字符轉換成01這樣的二進制數它長什麼樣子

1. 將原字符串的每一個char強轉換成 byte == ASCII
99 97 110 32 121 111 117 32 99 97 110 32 97 32 99 97 110 32 97 115 32 97 32 99 97 110 110 101 114 32 99 97 110 32 97 32 99 97 110
    
2. 將byte toBinaryString 轉換成01串如下:
1100011110000111011101000001111001110111111101011
0000011000111100001110111010000011000011000001100
0111100001110111010000011000011110011100000110000
1100000110001111000011101110100000110001111000011
1011101101110110010111100101000001100011110000111
011101000001100001100000110001111000011101110101110

也就是說,如果我們不對其進行壓縮時, 它將會轉換成上面那一大坨在網絡上進行傳輸

使用赫夫曼進行編碼:

思路: 我們將can you can a can as a canner can a can. 中的每一個符號,包括 點 空格,全部封裝進TreeNode

TreeNode中屬性如下: 包含權重: 也就是字符出現的次數, 包含data: 字符本身

public class TreeNode implements Comparable{
    // 存放權重就是字符出現的次數
    private int weight;
    // 存放英文數值
    private Byte data; //
    private TreeNode leftNode;
    private TreeNode rightNode;

封裝完成后, 按照權重的大小倒序排序,各個節點長成這樣:

a:11  :11   n:8   c:7   o:1  .:1  y:1   e:1  u:1  s:1  r:1  

將赫夫曼樹畫出來長這樣:

特徵,我們讓左側的路徑上的值是0, 右邊是1. 因此通過這個赫夫曼樹其實我們可以得到一張赫夫曼編碼錶,

比如像下面這樣:

n: 00
 : 01
a: 10
c: 111
// 每一個字符的編碼就是從根節點到它的路徑

有了這樣編碼錶, 下一步就是對數據進行編碼, 怎麼編碼呢? 不就是做一下替換嗎? 我們現在開始循環遍歷一開始的字符串, 挨個取出裏面的字符, 比如我們取出第一個字符是c, 拿着c來查詢這個表發現,c的編碼是111,於是我們將c替換成111, 遍歷到第二個字符是a, 拿着a查詢表,發現a的值是10, 於是我們將a替換成10, 重複這個過程, 最終我們得到的01串明顯比原來短很多

怎麼完成解碼呢? 解碼也不複雜, 前提也是我們得獲取到huffman編碼錶, 使用前綴匹配法, 比如我們現在接收到了

1111000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

使用前綴就是先取出1 去查查編碼錶有沒有這個數? 有的話就返回對應的字符, 沒有的話就用11再去匹配

大家可以看看上面的那顆霍夫曼樹, 所有的data都在恭弘=叶 恭弘子節點上,所以使用前綴匹配完全可以,絕對不會出現重複的情況

  • 使用java實現這個過程

思路概覽:

  1. 將原生的字節數組轉化成一個個的TreeNode
  2. 取出所有的TreeNode封裝成赫夫曼樹
  3. 通過赫夫曼樹踢去出赫夫曼編碼錶
  4. 使用這個編碼錶進行編碼
  5. 解碼

  private static byte[] huffmanZip(byte[] bytes) {
        // 先統計每個byte出現的次數,放入集合中
        List<TreeNode> treeNodes = buildNodes(bytes);
        // 創建赫夫曼樹
        TreeNode node = createHuffmanTree(treeNodes);
        // 創建huffman編碼錶
        Map<Byte, String> codes = createHuffmanCodeTable(node);
        // 編碼, 將每一個byte替換成huffman編碼錶中的V
        byte[] encodeBytes = encodeHuffmanByte(bytes, codes);
        
        // 使用huffman編碼進行解碼
        byte[] decodeBytes = decode(encodeBytes);
        return decodeBytes;
    }

將原生的byte數組,封裝成一個個的TreeNode節點,保存在一個容器中,並且記錄下這個節點出現的次數, 因此我們需要將出現次數多的節點靠近根節點

    /**
     * 將byte轉換成node集合
     *
     * @param bytes
     * @return
     */
    private static List<TreeNode> buildNodes(byte[] bytes) {
        ArrayList<TreeNode> list = new ArrayList<>();
        HashMap<Byte, Integer> countMap = new HashMap<>();
        // 統計每一個節點的出現的次數
        for (byte aByte : bytes) {
            Integer integer = countMap.get(aByte);
            if (integer == null) {
                countMap.put(aByte, 1);
            } else {
                countMap.put(aByte, integer + 1);
            }
        }
        // 將k-v轉化成node
        countMap.forEach((k, v) -> {
            list.add(new TreeNode(v, k));
        });
        return list;
    }

構建赫夫曼樹

  /**
     * 創建huffman樹
     *
     * @param treeNodes
     * @return
     */
    private static TreeNode createHuffmanTree(List<TreeNode> treeNodes) {
        // 開始循環, 當集合中只剩下一棵樹時
        while (treeNodes.size() > 1) {
            // 排序
            Collections.sort(treeNodes);
            // 取出權值最小的數
            TreeNode leftNode = treeNodes.get(treeNodes.size() - 1);
            // 取出權值次要小的數
            TreeNode rightNode = treeNodes.get(treeNodes.size() - 2);
            // 移除取出的兩棵樹
            treeNodes.remove(leftNode);
            treeNodes.remove(rightNode);

            // 創建新的樹根節點
            TreeNode parentNode = new TreeNode(leftNode.getWeight() + rightNode.getWeight(), leftNode, rightNode);
            // 將新樹放到原樹的集合中
            treeNodes.add(parentNode);
        }
        return treeNodes.get(0);
    }

從赫夫曼樹中提取出編碼錶, 思路: 下面是完了個遞歸, 我們規定好左樹是0,右邊是1, 通過一個SpringBuilder, 每次迭代都記錄下原來走過的路徑,當判斷到它的data不為空時,說明他就是恭弘=叶 恭弘子節點,立即保存這個節點曾經走過的路徑,保存在哪裡呢? 保存在一個map中, Key就是byte value就是走過的路徑

  static StringBuilder stringBuilder = new StringBuilder();
  static Map<Byte, String> huffCode = new HashMap<>();

    /**
     * 創建huffman便編碼錶
     *
     * @param node
     * @return
     */
    private static Map<Byte, String> createHuffmanCodeTable(TreeNode node) {
        if (node == null)
            return null;
        getCodes(node.getLeftNode(), "0", stringBuilder);
        getCodes(node.getRightNode(), "1", stringBuilder);
        return huffCode;
    }

    /**
     * 根據node, 獲取編碼
     *
     * @param node
     * @param code
     * @param stringBuilder
     */
    private static void getCodes(TreeNode node, String code, StringBuilder stringBuilder) {
        StringBuilder sb = new StringBuilder(stringBuilder);
        sb.append(code);
        // 如果節點的data為空,說明根本不是恭弘=叶 恭弘子節點,接着遞歸
        if (node.getData() == null) {
            getCodes(node.getLeftNode(), "0", sb);
            getCodes(node.getRightNode(), "1", sb);
        } else {
            // 如果是恭弘=叶 恭弘子節點,就記錄它的data和路徑
            huffCode.put(node.getData(), sb.toString());
        }
    }

根據赫夫曼編碼錶進行編碼:

思路:

舉個例子: 比如,原byte數組中的一個需要編碼的字節是a

a的ASCII==97

97正常轉成二進制的01串就是 0110 0001

但是現在我們有了編碼錶,就能根據97從編碼錶中取出編碼: 10

換句話說,上面 0110 0001 和 10 地位相同

若干個需要編碼的數append在一起,於是我們就有了一個比原來短一些的01串, 但是問題來了,到這裏就結束了嗎? 我們是將這些01串轉換成String, 在getBytes()返回出去嗎? 其實不是的,因為我們還需要進行解碼,你想想解碼不得編碼map中往外取值? 取值不得有key? 我們如果在這裏將這個01串的byte數組直接返回出去了,再按照什麼樣的方式將這個byte[]轉換成String串呢? ,因為我們要從這個String串中解析出key

然後這裏我們進行約定, 將現在得到的01串按照每8位為一組轉換成int數, 再將這個int強轉成byte, 解碼的時候我們就知道了.就按照8位一組進行解碼. 解析出來數組再轉換成01串,我們就重新拿到了這個編碼后的01串,它是個String串

每遇到8個0或者1,就將它強轉成Int, 再強轉成type, 經過這樣的轉換可能會出現負數,因此01串的最前面有個符號位,1表示負數

比如說: 如果你打印一下面代碼中的encodeByte,你會發現打印的第一個數是-23, 這個-23被保存在新創建的byte數組的第一個位置上, 後續解碼時,就從這個byte數組中的第一個位置上獲取出這個-23, 將它轉換成01二進制串

怎麼轉換呢? 比如不是-23, 而是-1
真值 1
原碼:1,0001
補碼: 2^(4+1) +1 = 100000 + (-1) = 1,1111
我們獲取到的結果就是1111
 /**
     * 進行編碼
     *
     * @param bytes
     * @param codes
     * @return
     */
    private static byte[] encodeHuffmanByte(byte[] bytes, Map<Byte, String> codes) {
        StringBuilder builder = new StringBuilder();
        for (byte aByte : bytes) {
            builder.append(codes.get(aByte));
        }

        // 將這些byte按照每8位一組進行編碼
        int length = 0;
        if (builder.length() % 8 == 0) {
            length = builder.length() / 8;
        } else {
            length = builder.length() / 8 + 1;
        }
        // 用於存儲壓縮后的byte
        byte[] resultByte = new byte[length];
        // 記錄新byte的位置
        int index = 0;
        // 遍歷新得到的串
        for (int i = 0; i < builder.length(); i += 8) {
            String str = null;
            if (i + 8 > builder.length()) {
                str = builder.substring(i);
            } else {
                str = builder.substring(i, i + 8);
            }
            // 將八位的二進制轉換成byte
            // 這裏出現負數了....  涉及到補碼的問題
            byte encodeByte = (byte) Integer.parseInt(str, 2);
            // 存儲起來
            resultByte[index] = encodeByte;
            index++;
        }
        return resultByte;
    }

解碼: 前面我們知道了,約定是按照8位轉換成的int 再轉換成type[] , 現在按照這個約定,反向轉換出我們一開始的01串

/**
     * 按照指定的赫夫曼編碼錶進行解碼
     *
     * @param encodeBytes
     * @return
     */
    private static byte[] decode(byte[] encodeBytes) {
        List<Byte> list = new ArrayList();
        StringBuilder builder = new StringBuilder();
        for (byte encodeByte : encodeBytes) {
            // 判斷是否是最後一個,如果是最後一次不用用0補全, 因此最後一位本來就不夠8位
            boolean flag = encodeByte == encodeBytes[encodeBytes.length - 1];
            String s = byteToBitStr(!flag, encodeByte);
            builder.append(s);
        }
        // 調換編碼錶的k-v
        Map<String, Byte> map = new HashMap<>();
        huffCode.forEach((k, v) -> {
            map.put(v, k);
        });
        // 處理字符串
        for (int i = 0; i < builder.length(); ) {
            int count = 1;
            boolean flag = true;
            Byte b = null;
            while (flag){
                String key = builder.substring(i,i+count);
                b=map.get(key);
                if (b==null){
                    count++;
                }else {
                    flag=false;
                }
            }
            list.add(b);
            i+=count;
        }

        // 將list轉數組
        byte[] bytes = new byte[list.size()];
        int i=0;
        for (Byte aByte : list) {
            bytes[i]=aByte;
            i++;
        }
        return bytes;
    }

    /**
     * 將byte轉換成二進制的String
     *
     * @param b
     * @return
     */
    public static String byteToBitStr(boolean flag, byte b) {
        /**
         * 目標: 全部保留八位.正數前面就補零, 負數前面補1
         * 為什麼選256呢?  因為我們前面約定好了, 按照8位進行分隔的
         * 256的二進製表示是  1 0000 0000
         * 假設我們現在是 1
         * 計算              1 0000 0000
         *               或  0 0000 0001
         *              ----------------------
         *                   1 0000 0001
         *                   結果截取8位就是 0000 0001
         *
         * 假設我們現在是   -1
         * 轉換成二進制:    1111 1111 1111 1111 1111 1111 1111 1111
         *
         * 計算                            1 0000 0000
         * 或  1111 1111 1111 1111 1111 1111 1111 1111
         *              ----------------------
         *                        1 1111 1111
         *                   結果截取8位就是 1111 1111
         *
         *
         */
        int temp = b;
        if (flag) {
            temp |= 256;
        }
        String str = Integer.toBinaryString(temp);
        if (flag) {
            return str.substring(str.length() - 8);
        } else {
            return str;
        }

    }

二叉排序樹

二叉排序樹, 又叫二叉搜索樹 , BST (Binary Search Tree)

  • 線性存儲和鏈式存儲的優缺點

比如我們有一個數組 [7,3,10,12,5,1,9]

雖然我們可以直接取出下標為幾的元素,但是卻不能直接取出值為幾的元素, 比如,我們如果想取出值為9的元素的話,就得先去遍歷這個數組, 然後挨個看看當前位置的數是不是9 , 就這個例子來說我們得找7次

假設我們手裡的數組已經是一個有序數組了 [1,3,5,7,9,11,12]

我們可以通過二分法快速的查找到想要的元素,但是對它依然是數組,如果想往第一個位置上插入元素還是需要把從第一個位置開始的元素,依次往後挪. 才能空出第一個位置,把新值放進去

假設我們將這一行數轉換成鏈式存儲, 確實添加, 刪除變的異常方便, 但是查找還是慢, 不管是查詢誰, 都得從第一個開始往後遍歷

  • 我們的主角: 二叉搜索樹

二叉排序樹有如下的特點:

  • 對於二叉排序樹中的任意一個非恭弘=叶 恭弘子節點都要求他的左節點小於自己, 右節點大於自己
  • 空樹也是二叉排序樹

將上面的無序的數組轉換成二叉排序樹長成下圖這樣

如果我們按照中序遍歷的話結果是: 1 3 5 7 9 11 12 , 正好是從小到大完成排序

再看他的特徵: 如果我們想查找12 , 很簡單 7-10-12 , 如果我們想插入也很簡單,它有鏈表的特性

java&二叉排序樹

封裝Node和Tree

// tree
public class BinarySortTree {
    Node root;
}

// node
public class Node {
    private int value;
    private Node leftNode;
    private Node rightNode;
}

構建一顆二叉排序樹, 思路是啥呢? 如果沒有根節點的話,直接返回,如果存在根節點, 就調用根節點的方法,將新的node添加到根節點上, 假設我們現在遍歷到的節點是NodeA. 新添加的節點是NodeB, 既然想添加就得比較一下NodeA和NodeB的值的大小, 將如果NodeB的值小於NodeA,就添加在NodeA的右邊, 反之就添加在NodeA的左邊

-----------BinarySortTree.class--------------- 
/**
     * 向二叉排序樹中添加節點
     */
    public void add(Node node) {
        if (root == null) {
            root = node;
        } else {
            root.add(node);
        }
    }

-------------Node.class------------
/**
     * 添加節點
     *
     * @param node
     */
    public void add(Node node) {
        if (node == null)
            return;
        //判斷需要添加的節點的值比傳遞進來的節點的值大還是小
        // 添加的節點小於當前節點的值
        if (node.value < this.value) {
            if (this.leftNode == null) {
                this.leftNode = node;
            } else {
                this.leftNode.add(node);
            }
        } else {
            if (this.rightNode == null) {
                this.rightNode = node;
            } else {
                this.rightNode.add(node);
            }
        }
    }

刪除一個節點

刪除一節點如如下幾種情況, 但是無論是哪種情況,我們都的保存當前節點的父節點, 通過他的父節點對應節點=null實現節點的刪除

情況1: 如圖

這是最好處理的情況, 就是說需要刪除的元素就是單個的子節點

情況2: 如圖

這種情況也不麻煩,我們讓當前比如我們想上刪除上圖中的3號節點, 我們首先保存下node3的父節點 node7, 刪除node3時發現node3有一個子節點,於是我們讓 node7 的 leftNode = node3

情況3: 如圖

比如我們想刪除7, 但是7這個節點還有一個子樹 按照中序遍歷這個樹的順序是 1,3,5,7,9,11,13, 想刪除7的話,其實

  1. 臨時存儲node9
  2. 刪除node9
  3. 用臨時存儲的node9替換node7

如果node9還有右節點怎麼辦呢?

  1. 臨時保存node9
  2. 刪除node9
  3. 讓node9的右節點替換node9
  4. 讓臨時存儲的node9替換node7
/**
     * 刪除一個節點
     *
     * @param value
     * @return
     */
    public void delete(int value) {
        if (root == null) {
            return;
        } else {
            // 找到這個節點
            Node node = midleSearch(value);
            if (node == null)
                return;
            // 找到他的父節點
            Node parentNode = searchParent(value);

            // todo 當前節點是恭弘=叶 恭弘子節點
            if (node.getLeftNode() == null && node.getRightNode() == null) {
                if (parentNode.getLeftNode().getValue() == value) {
                    parentNode.setLeftNode(null);
                } else {
                    parentNode.setRightNode(null);
                }
                // todo 要刪除的節點存在兩個子節點
            } else if (node.getLeftNode() != null && node.getRightNode() != null) {
                // 假設就是刪除7
                //1. 找到右子樹中最小的節點,保存它的值,然後刪除他
                int minValue = deleteMin(node.getRightNode());
                //2.替換被刪除的節點值
                node.setValue(minValue);

            } else { // todo 要刪除的節點有一個左子節點或者是右子節點
                // 左邊有節點
                if (node.getLeftNode() != null) {
                    // 要刪除的節點是父節點的左節點
                    if (parentNode.getLeftNode().getValue() == value) {
                        parentNode.setLeftNode(node.getLeftNode());
                    } else {// 要刪除的節點是父節點的右節點
                        parentNode.setRightNode(node.getLeftNode());
                    }
                } else { // 右邊有節點
                    // 要刪除的節點是父節點的右節點
                    if (parentNode.getLeftNode().getValue() == value) {
                        parentNode.setLeftNode(node.getRightNode());
                    } else {// 要刪除的節點是父節點的右節點
                        parentNode.setRightNode(node.getRightNode());
                    }
                }
            }
        }
    }

 /**
     * 刪除並保存以當前點為根節點的樹的最小值節點
     * @param node
     * @return
     */
    private int deleteMin(Node node) {
        // 情況1: 值最小的節點沒有右節點
        // 情況2: 值最小的節點存在右節點
        // 但是下面我們使用delete,原來考慮到了
        while(node.getLeftNode()!=null){
            node=node.getLeftNode();
        }
        delete(node.getValue());
        return node.getValue();
    }

    /**
     * 搜索父節點
     *
     * @param value
     * @return
     */
    public Node searchParent(int value) {
        if (root == null) {
            return null;
        } else {
            return root.searchParent(value);
        }
    }

缺點

二叉排序樹其實對節點權是有要求的, 比如我們的數組就是[1,2,3,4] 那麼畫成平衡二叉樹的話長下面這樣

它不僅沒有二叉排序樹的優點,而且還不如單鏈表的速度快

AVL樹(平衡二叉樹)

定義: 什麼是平衡二叉樹

平衡二叉樹的出現就是為了 解決上面二叉排序樹[1,2,3,4,5,6]這樣成單條鏈的略勢的情況,它要求,每個樹的左子樹和右子樹的高度之差不超過1, 如果不滿足這種情況了,馬上馬對各個節點進行調整,這樣做保證了二叉排序樹的優勢

如何調整

  • 情況1: 對於node1來說, 它的左邊深度0 , 右邊的深度2 , 於是我們將它調整成右邊的樣子
  • 情況2: 在1234的情況下, 添加node5,導致node2不平衡, 進行如下的調整
  • 情況3: 在12345的基礎上添加node6,導致node4不平衡, 對node4進行調整, 其實就和情況1相同了
  • 情況4: 在1234567的情況下,進行添加8. 打破了node5的平衡, 因此進行旋轉

一個通用的旋轉規律

看這個典型的有旋轉的例子

node4的出現,使用node8的平衡被打破, 因此我們需要進行調整, 按照下面的步驟進行調整

下面說的this是根節點node8, 按照下面的步驟在紙上畫一畫就ok

  1. 創建新node, 使新node.value = this.value
  2. 新節點的rightNode = this.rightNode
  3. 新節點的leftNode = this.leftNode.rightNode
  4. this.value = this.LeftNode.value
  5. this.leftNode = this.leftNode .leftNode
  6. this.leftNode = 新創建的node

需要注意的情況:

新添加6使得node8不再平衡,但是如果你按照上面的步驟進行旋轉的話,會得到右邊的結果, 但是右邊的結果中對於node4還是不平衡的,因此需要預處理一下

再進行右旋轉時,提前進行檢驗一下,當前節點的左子樹是否存在右邊比左邊高的情況, 如果右邊比較高的話,就先將這個子樹往左旋轉, 再以node8為根,整體往右旋轉

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

2015年宇通共獲新能源汽車補貼逾68億

4月4日,宇通客車發佈2015年年報。報告期內,累計完成客車銷售 67,018 輛,實現營業收入312.1 億元,實現歸屬于母公司所有者淨利潤 35.35 億元。公司新能源客車合計生產20568輛,同比增長173.91%;銷售20446台,同比增長 176.1%。其中純電動客車銷量為13885輛,同比增長706.8%;插電式混合動力客車銷量6560輛,同比增長17.8%。

在收入方面,純電動客車營收約92.62億元,插電式混合動力客車營收47.13億元。公告指出,報告期內,公司抓住了新能源市場的爆發式增長的機會,營業收入同比增長 21.31%,歸屬于母公司所有者的淨利潤同比增長 35.31%,經營活動現金淨流量 60.1 億元。

2015年宇通客車新能源汽車產銷及獲得補貼統計(來源:宇通客車2015年年報)

據瞭解,2015年宇通客車共獲得新能源汽車推廣應用補貼68.565億元。其中純電動客車補貼52.345億元,插電式混合動力客車補貼16.22億元。從獲得的補貼比例來看,純電動客車佔據56.52%的份額,插電式混合動力客車占比34.41%。

根據公告,新能源客車的關鍵零部件中,整車控制系統為自主研發自主生產,集成式電機控制器、電機、超級電容和動力電池系統均與行業綜合實力較強的供應商聯合開發,通過整合行業資源,研製出技術領先有競爭力的零部件,支撐公司新能源客車的技術領先。

報告指出,新能源市場受國家補貼政策的推動快速發展,大量企業切入客車市場,競爭情況更加複雜,隨著補貼政策的進一步完善,產品經過時間的檢驗,技術實力強、產品優質、綜合性價比高的企業將在未來新能源客車市場中居於主導地位。

根據公告,2015年,宇通客車實現了全產品端新能源產品的覆蓋,在節能技術、NVH 技術、無人駕駛技術等方面開展了深入研究,整體研發實力進一步增強。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

SpEL + AOP實現註解的動態賦值.

一、自定義註解

先聊聊這個需求,我需要根據用戶的權限對數據進行一些處理,但是痛點在哪裡呢?用戶的權限是在請求的時候知道的,我怎麼把用戶的權限傳遞給處理規則呢?想了以下幾種方案:

  1. Mybatis 攔截器:如果你的權限參數可以滲透到 Dao 層,那麼這是最好的處理方式,直接在 Dao 層數據返回的時候,根據權限做數據處理。
  2. Dubbo 過濾器:如果 Dao 層沒辦法實現的話,只好考慮在 service 層做數據處理了。
  3. ResponseBodyAdvice :要是 service 層也沒辦法做到,只能在訪問層數據返回的時候,根據權限做數據處理。(以下介紹的正是這種方式)

那麼現在有個難點就是:我怎麼把 request 的權限參數傳遞到 response 中呢?當然可以在 Spring 攔截器中處理,但是我不想把這段代碼侵入到完整的鑒權邏輯中。突然想到,我能不能像 spring-data-redis 中 @Cacheable 一樣,利用註解和 SpEL 表達式動態的傳遞權限參數呢?然後在 ResponseBodyAdvice 讀取這個註解的權限參數,進而對數據進行處理。

首先,我們需要有個自定義註解,它有兩個參數:key 表示 SpEL 表達式;userType 表示權限參數。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseSensitiveOverride {

    /**
     * SPEL 表達式
     *
     * @return
     */
    String key() default "";

    /**
     * 1:主賬號、2:子賬號
     */
    int userType() default 1;
}

然後,把這個註解放在路由地址上,key 寫入獲取權限參數的 SpEL 表達式:

    @ResponseSensitiveOverride(key = "#driverPageParam.getUserType()")
    @RequestMapping(value = "/queryPage", method = RequestMethod.POST)
    public ResponseData<PageVo<AdminDriverVo>> queryPage(@RequestBody AdminDriverPageParam driverPageParam) {
        return driverService.queryPageAdmin(driverPageParam);
    }

二、SpEl + AOP 註解賦值

現在 SpEL 表達式是有了,怎麼把 SpEL 表達式的結果賦值給註解的 userType 參數呢?這就需要用 、 和 的知識。

@Aspect
@Component
public class SensitiveAspect {

    private SpelExpressionParser spelParser = new SpelExpressionParser();

    /**
     * 返回通知
     */    
    @AfterReturning("@annotation(com.yungu.swift.base.model.annotation.ResponseSensitiveOverride) && @annotation(sensitiveOverride)")
    public void doAfter(JoinPoint joinPoint, ResponseSensitiveOverride sensitiveOverride) throws Exception {
        //獲取方法的參數名和參數值
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames());
        List<Object> paramList = Arrays.asList(joinPoint.getArgs());

        //將方法的參數名和參數值一一對應的放入上下文中
        EvaluationContext ctx = new StandardEvaluationContext();
        for (int i = 0; i < paramNameList.size(); i++) {
            ctx.setVariable(paramNameList.get(i), paramList.get(i));
        }

        // 解析SpEL表達式獲取結果
        String value = spelParser.parseExpression(sensitiveOverride.key()).getValue(ctx).toString();
        //獲取 sensitiveOverride 這個代理實例所持有的 InvocationHandler
        InvocationHandler invocationHandler = Proxy.getInvocationHandler(sensitiveOverride);
        // 獲取 invocationHandler 的 memberValues 字段
        Field hField = invocationHandler.getClass().getDeclaredField("memberValues");
        // 因為這個字段是 private final 修飾,所以要打開權限
        hField.setAccessible(true);
        // 獲取 memberValues
        Map memberValues = (Map) hField.get(invocationHandler);
        // 修改 value 屬性值
        memberValues.put("userType", Integer.parseInt(value));

    }
}

通過這種方式,我們就實現了為註解動態賦值。

三、ResponseBodyAdvice 處理數據

現在要做的事情就是在 ResponseBody 數據返回前,對數據進行攔截,然後讀取註解上的權限參數,從而對數據進行處理,這裏使用的是 SpringMVC 的 ResponseBodyAdvice 來實現:

@Slf4j
@RestControllerAdvice
@Order(-1)
public class ResponseBodyAdvice implements org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice {

    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return SysUserDto.USER_TYPE_PRIMARY;
        }
    };

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        if (returnType.hasMethodAnnotation(ResponseSensitiveOverride.class)) {
            ResponseSensitiveOverride sensitiveOverride = returnType.getMethodAnnotation(ResponseSensitiveOverride.class);
            threadLocal.set(sensitiveOverride.userType());
            return true;
        }
        return false;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body != null && SysUserDto.USER_TYPE_SUB.equals(threadLocal.get())) {
            // 業務處理
        }
        return body;
    }
}

題外話,其實我最後還是擯棄了這個方案,選擇了 Dubbo 過濾器的處理方式,為什麼呢?因為在做數據導出的時候,這種方式沒辦法對二進制流進行處理呀!汗~ 但是該方案畢竟耗費了我一個下午的心血,還是在此記錄一下,可能有它更好的適用場景!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?