天天看點

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

數組

數組在編碼中很常見,就是把資料碼成一排存放。

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

這就表示一個數組,這個數組有八個元素存放。對于元素的擷取,主要就是通過下标擷取,是以索引對于數組是很重要的,這個索引可以是有意義的,也可以是沒有意義的。比如array【2】這個數組,可以是僅僅代表下标,也可以是有一個意義在裡面,代表學号分數等等。Java裡面有存在靜态數組,直接int[]指派,但是這種方法是不能動态初始化的,我們二次封裝一個:

public class Array<E> {
    private E[] data;
    private int size;

    public Array(int capacity) {
        data = (E[]) new Object[capacity];
        size = 0;
    }

    public Array(){
        this(10);
    }

    public int getSize(){
        return size;
    }

    public int getCapacity(){
        return data.length;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    public void addLast(E e){
        add(size, e);
    }

    public void addFirst(E e){
        add(0, e);
    }

    public void add(int index, E e){
        if (size == data.length){
            resize(2 * data.length);
        }
        if (index < 0 || index > size){
            throw new IllegalArgumentException("index must be in range[0, size]");
        }
        for (int i = size-1; i >= index; i --) {
            data[i+1] = data[i];
        }
        data[index] = e;
        size ++;
    }

    public E get(int index){
        if (index < 0 || index > size){
            throw new IllegalArgumentException("index must be in range[0, size]");
        }
        return data[index];
    }

    public void set(int index, E e){
        if (index < 0 || index > size){
            throw new IllegalArgumentException("index must be in range[0, size]");
        }
        data[index] = e;
    }

    public E remove(int index){
        if (index < 0 || index > size){
            throw new IllegalArgumentException("index must be in range[0, size]");
        }
        E ret = data[index];
        for (int i = index + 1; i < size; i ++){
            data[i-1] = data[i];
        }
        size --;
        return ret;
    }

    private void resize(int newCapacity){
        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    @Override
    public String toString(){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(String.format("Array: size = %d , capacity = %d\n", size, data.length));
        stringBuilder.append('[');
        for (int i = 0; i < size; i++) {
            stringBuilder.append(data[i]);
            if (i != size-1){
                stringBuilder.append(", ");
            }
        }
        stringBuilder.append(']');
        return stringBuilder.toString();
    }
}

           

在添加的時候如果遇到空間不夠會自動進行擴容操作,擴容兩倍,按照兩倍人容量建立一個新數組,然後把原數組的内容複制進去,再把原數組指向新數組即可。對于數組的複雜度,由于添加和删除的期望都是

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

,是以複雜度都是O(n),而修改這些就是O(1)了。

棧原理

首先要了解一下什麼是線性表,線性表是一個線性的資料結構,含有n>=0個節點的有限序列,對于其中的一個節點,有一個沒有前驅隻有一個後繼的開始節點和一個隻有前驅沒有後繼的結束的節點。線性表和數組的差別還是很大的,這是兩種不同的資料結構,數組有次元的概念,線性表沒有,線性表有前驅後繼的概念,各個元素之間是有聯系的,數組沒有。線性表可以使用一維的數組存儲,但是并不代表線性表就是一維數組。

stack棧就是一種特殊的線性表,先進後出,操作資料這端就叫做棧頂,表尾稱為棧尾,所有的操作隻能從棧頂開始,無論是取出還是删除操作,都隻能從一端操作。棧的基本操作就三個:push壓棧,pop出棧,peek檢視棧頂元素。

package Stack;

public class Stack {
    private int[] datas;
    private int tapIndex = -1;
    private int lenght = 0;

    public Stack(int lenght){
        this.lenght = lenght;
        datas = new int[this.lenght];
    }

    public void push(int data){
        datas[++tapIndex] = data;
    }

    public int pop(){
        return datas[tapIndex --];
    }

    public int peek(){
        return datas[tapIndex];
    }

    public boolean isEmpty(){
        return tapIndex == -1;
    }

    public boolean isFull(){
        return tapIndex == (lenght - 1);
    }
}
           

應用

①括号比對

棧的實作還有一種連結清單的形式,這裡不寫了。 棧的應用很多,字元串的倒序,括号比對,計算算術表達式。字元串反轉這個比較簡單,不用解釋,括号比對,如果進來的是括号的左邊,那麼就入棧,遇到一個右邊的括号就出棧,看看這個時候出棧的元素和目前的元素是不是比對的,如果是繼續,不是直接傳回false就好了。

public static boolean checkBrackets(String string) {
        char[] charString = string.toCharArray();
        Stack stack = new Stack(10);
        for (int i = 0; i < charString.length; i++) {
            char c = charString[i];
            if (c == '{' || c == '[' || c == '(') {
                stack.push(c);
            } else if (c == '}' || c == ']' || c == ')') {
                char popElement = (char) stack.pop();
                if ((popElement == '{' && c != '}')
                        || (popElement == '[' && c != ']')
                        || (popElement == '(' && c != ')')
                ) {
                    return false;
                } else {
                    continue;
                }
            } else {
                continue;
            }
        }
        if (stack.isEmpty())
            return true;
        else {
            return false;
        }
    }
           
Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼
②計算算術

類似電腦上的計算,給出一條式子計算他的結果。首先要提到的就是逆波蘭表達式,也稱為是字尾表達式,這種表達式是把運算符号寫在了運算對象後面,比如a+b寫成ab+,是以也叫字尾表達式。當然有字尾自然就有字首,隻不過字首用的比較少一般都是用字尾。這種方法的優點就是根據運算對象和運算符的出現次序進行計算,不需要使用括号,也便于程式實作求值。中綴轉字尾是不需要做任何的計算:

1,從左到右順序讀取表達式的字元。

2,是操作數那麼就指派到字尾表達式的字元串。

3,是左括号就把字元壓進棧中。

4,是右括号,從棧中彈出符号到字尾表達式,直到遇到左括号,然後把左括号彈出。

5,是操作符,如果這個時候棧頂元素操作符的優先級大于或者等于此操作符,彈出棧頂操作符到字尾表達式,直到遇到優先級更低的元素的位置,把操作符壓入棧。

public String midToPost() {
        String postString = "";
        Stack stack = new Stack(str.length() + 1);
        char[] charString = str.toCharArray();
        for (int i = 0; i < charString.length; i++) {
            char c = charString[i];
            if (c >= '0' && c <= '9') {
                postString += c;
            } else if (c == '(') {
                stack.push(c);
            } else if (c == ')') {
                char pop = (char) stack.pop();
                while (pop != '(') {
                    postString += pop;
                    pop = (char) stack.pop();
                }
            } else {
                if (!stack.isEmpty()){
                    char pop = (char) stack.pop();
                    if (pop == '('){
                        stack.push(pop);
                        stack.push(c);
                        continue;
                    }
                    if (map.get(pop) > map.get(c)){
                        postString += pop;
                        stack.push(c);
                    }else {
                        stack.push(pop);
                        stack.push(c);
                    }
                }else {
                    stack.push(c);
                }
            }
        }

        while (!stack.isEmpty()){
            char pop = (char) stack.pop();
            postString += pop;
        }

        return postString;
    }
           

中綴表達式轉字尾表達式,按照上面的步驟進行判斷即可。裡面用到了一些優先級的比較,我把運算符都用一些數字表達。比較優先級的時候比較數字大小就好了。

public CalculatePost(String str) {
        this.str = str;
        map.put('+', 0);
        map.put('-', 0);
        map.put('*', 1);
        map.put('/', 1);
    }
           

然後就是計算字尾表達式:

public int getValue(String str1) {
        char[] charString = str1.toCharArray();
        Stack stack = new Stack(charString.length + 1);
        for (int i = 0; i < charString.length; i++) {
            char c = charString[i];
            if (c >= '0' && c <= '9') {
                stack.push((int) (c - '0'));
            } else {
                int num2 = stack.pop(), num1 = stack.pop(), temp = 0;
                if (c == '+') {
                    temp = num2 + num1;
                } else if (c == '-') {
                    temp = num1 - num2;
                } else if (c == '*') {
                    temp = num1 * num2;
                } else if (c == '/') {
                    temp = num1 / num2;
                }
                stack.push(temp);
            }
        }

        return stack.pop();
    }
           

最後測試用例:

public static void main(String args[]){
        String str = "((3+2)*(6+7))*5";
        CalculatePost calculatePost = new CalculatePost(str);
        System.out.println(calculatePost.midToPost());
        System.out.println(calculatePost.getValue(calculatePost.midToPost()));
     }
           
Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

隊列

隊列原理

隊列是一種特殊的線性表,它和棧有點相似,棧是先進後出,隊列是先進先出,和排隊買票一樣,但是就是不能插隊。隊列有兩個操作點,一個是隊尾,隻能做插入,另一個是隊頭,隻能取元素。隊列的基本操作有三個,插入隊列,出隊列,檢視隊頭元素。隊列有兩種實作方式,一種是數組實作,一種是連結清單實作,連結清單實作比較簡單。但是數組實作會出現資源浪費的情況,一出一進整個隊列就會往前移動,有部分空間就使用不了了,是以就出現了循環隊列。下面用數組實作循環隊列:

public class Queue {
    private int [] queue;
    private int front;
    private int end;
    private int nItem;

    public Queue(int count){
        front = 0;
        end = 0;
        nItem = count;
        queue = new int[count];
    }

    public void insert(int data){
        if ((end+1)%nItem == front){
            System.err.println("Queue is Full");
        }else {
            queue[end] = data;
            end = (end+1)%nItem;
        }
    }

    public void remove(){
        if ((end-front + nItem)%nItem == 0){
            System.err.println("Queue is empty!");
        }else {
            front = (front + 1)%nItem;
        }
    }

    public int peekFront(){
        return queue[front];
    }

    public boolean isEmpty(){
        return (end-front + nItem)%nItem == 0;
    }

    public boolean isFull(){
        return (++end)%nItem == front;
    }

    public int count(){
        return (end-front + nItem)%nItem;
    }

     public static void main(String args[]){
        Queue queue = new Queue(11);
        for (int i = 0;i < 10; i++){
            queue.insert(i);
        }
        System.out.println(queue.peekFront());
        queue.remove();
        System.out.println(queue.peekFront());
        queue.remove();
        queue.insert(13);
     }
}
           

由于整個隊列一删一添的操作會導緻整個隊列移動,是以要對前後辨別做循環處理,求餘數來得到下一個位置,這樣加上一永遠是再最大範圍裡面,而且為了友善判斷是否滿,會留出一個空間來做判斷。除了最基本的隊列,還有雙端隊列和優先隊列,雙端隊列比較少用。優先隊列和普通隊列的排列方式不一樣,優先隊列是按照優先級出隊,每一次插入元素隊列動态按照優先級維護,少用可以用堆這種資料結構。實作優先隊列:

public class PrioriityQueue {

    private int [] queue;

    private int nItem;

    private int capacity;

    public PrioriityQueue(int count){
        queue = new int[count];
        nItem = 0;
        capacity = count;
    }

    public void insert(int data){
        if (nItem == 0){
            queue[nItem] = data;
            nItem ++;
        }else {
            int i = 0;
            for (i = nItem - 1; i >= 0; i--){
                if (data > queue[i]){
                    queue[i+1] = queue[i];
                }else {
                    break;
                }
            }
            queue[i+1] = data;
            nItem ++;
        }
    }

    public int remove(){
        int temp = queue[0];
        for (int i = 0; i < nItem-2; i++){
            queue[i] = queue[i+1];
        }
        nItem --;
        return temp;
    }

    public int peekFront(){
        if (nItem == 0){
            System.err.println("PriorityQueue is empty!!!");
        }
        int temp = queue[0];
        return temp;
    }

     public static void main(String args[]){
        PrioriityQueue prioriityQueue = new PrioriityQueue(10);
        Random random = new Random();
        for (int i = 0; i < 8; i++){
            int num = random.nextInt();
            prioriityQueue.insert(num);
        }

         for (int i = 0; i < 8; i++) {
             System.out.println(prioriityQueue.remove());
         }
     }

}

           

連結清單

連結清單原理

連結清單是一類特殊的線性表,由一系列點組成,節點順序是通過節點元素的指針連接配接次序進行。連結清單中節點包含兩部分,一個是自身的資料,一個是指向下一個節點的引用。連結清單和數組:這兩種資料結構都可以作為資料的存儲結構,但是數組是順序存放,長度是固定的,而連結清單長度不限制,不是連續的存儲。另外從效率上說,連結清單在插入和删除操作上效率很高,因為隻需要改變各自的索引,不需要移動等等。而數組就牛逼了,在某一個地方添加元素,後面的都要全部往後移動,删除全部要往前移動,但是數組在查詢上是高于連結清單的,直接索引即可,是以基本能用數組的地方也可以用連結清單。連結清單的基本操作:插入資料,删除資料,檢視所有資料,查找指定節點,删除指定節點。連結清單基本實作。

首先是定義一個節點,一個節點應該有目前資料和下一個節點的資料:

public class LinkNode {
    private int id;
    private LinkNode next;

    public int getId() {
        return id;
    }

    public LinkNode getNext() {
        return next;
    }

    public void setNext(LinkNode node) {
        this.next = node;
    }

    public void setId(int id) {
        this.id = id;
    }

    public LinkNode(int id){
        super();
        this.id = id;
        this.next = null;
    }

    public void printNode(){
        String s = "id = " + id + " ";
        if (next != null){
            s += ",next = " + next.getId() + " ";
        }
        System.out.print(s);
    }
}

           

然後就是基本的删除添加操作了:

public class SingleLinkList {
    private LinkNode firstNode;
    private int count;

    public SingleLinkList() {
        firstNode = null;
        count = 0;
    }

    public void insertFirst(int id) {
        LinkNode linkNode = new LinkNode(id);
        linkNode.setNext(firstNode);
        firstNode = linkNode;
        count++;
    }

    public void insertNode(int id, int index) {
        if (index < 0 || index > count + 1) {
            throw new IllegalArgumentException("index must be in range[0, N+1]");
        }
        LinkNode linkNode = new LinkNode(id);
        if (index == 1) {
            linkNode.setNext(firstNode);
            firstNode = linkNode;
            count++;
            return;
        }
        LinkNode p = firstNode, q = p;
        for (int i = 0; i < index - 1; i++) {
            q = p;
            p = p.getNext();
        }
        q.setNext(linkNode);
        linkNode.setNext(p);
        count++;
    }

    public LinkNode removeNode(int index) {
        LinkNode p = firstNode, q = p, a = p;
        if (index < 0 || index > count) {
            throw new IllegalArgumentException("index must be in range[0, N]");
        }
        if (index == 1) {
            p = firstNode;
            firstNode = firstNode.getNext();
            count--;
            return p;
        }
        for (int i = 0; i < index - 1; i++) {
            a = p;
            p = p.getNext();
            q = p.getNext();
        }
        a.setNext(q);
        return p;

    }

    public LinkNode find(int id) {
        LinkNode linkNode = firstNode;
        while (linkNode != null){
            if (linkNode.getId() == id){
                return linkNode;
            }
            linkNode = linkNode.getNext();
        }
        return null;
    }

    public void display() {
        LinkNode linkNode = firstNode;
        while (linkNode != null) {
            linkNode.printNode();
            linkNode = linkNode.getNext();
        }
        System.out.println();
    }

    public int count(){
        return count;
    }


    public static void main(String args[]) {
        SingleLinkList singleLinkList = new SingleLinkList();
        for (int i = 0; i < 10; i++) {
            singleLinkList.insertFirst(i);
        }
        singleLinkList.display();
        singleLinkList.insertNode(15, 11);
        singleLinkList.removeNode(5);
        singleLinkList.display();
    }
}

           

都比較簡單。

有單向連結清單自然就有雙向連結清單。單向連結清單隻是有一個指向下一個節點的資訊,而雙向連結清單就是包含指向了前一個和後一個節點資訊的。這樣做帶來了節點的開銷,但是對于插入删除帶來了極大的便利,原來單向連結清單需要三個變量來控制,現在用雙向連結清單,隻需要一個就好了,因為通過這個節點就可以獲得前後節點,然後前一個節點指向後一個,後一個節點指回前一個即可。

public class FirstLastList {
    private int count;
    private DouleLinkNode firstNode;

    public FirstLastList() {
        count = 0;
        firstNode = null;
    }

    public void insertFirst(int id) {
        DouleLinkNode douleLinkNode = new DouleLinkNode(id);
        DouleLinkNode p = firstNode;
        if (p != null)
            p.setPrevious(douleLinkNode);
        douleLinkNode.setNext(firstNode);
        firstNode = douleLinkNode;
        count++;
    }

    public void insetNode(int id, int index) {
        if (index < 0 || index > count + 1) {
            throw new IllegalArgumentException("index must be in range[0, N+1]");
        }
        if (index == 1) {
            insertFirst(id);
            count++;
            return;
        }
        DouleLinkNode douleLinkNode = new DouleLinkNode(id);
        DouleLinkNode p, q;
        p = q = firstNode;
        for (int i = 0; i < index - 1; i++) {
            q = p;
            p = p.getNext();
        }
        douleLinkNode.setNext(p);
        q.setNext(douleLinkNode);
        douleLinkNode.setPrevious(q);
        count++;
    }

    public DouleLinkNode removeNode(int index) {
        if (index < 0 || index > count) {
            throw new IllegalArgumentException("index must be in range[0, N]");
        }
        DouleLinkNode p = firstNode;

        if (index == 1) {
            firstNode = firstNode.getNext();
            firstNode.setPrevious(null);
            count--;
            return p;
        }

        for (int i = 0; i < index - 1; i++) {
            p = p.getNext();
        }
        DouleLinkNode q, l;
        q = p.getPrevious();
        l = p.getNext();
        q.setNext(l);
        l.setPrevious(q);
        count--;
        return p;
    }

    public void display() {
        DouleLinkNode linkNode = firstNode;
        while (linkNode != null) {
            linkNode.printNode();
            linkNode = linkNode.getNext();
        }
        System.out.println();
    }

    public static void main(String args[]) {
        FirstLastList singleLinkList = new FirstLastList();
        for (int i = 0; i < 10; i++) {
            singleLinkList.insertFirst(i);
        }
        singleLinkList.display();
        singleLinkList.insetNode(15, 5);
        singleLinkList.removeNode(1);
        singleLinkList.display();
    }
}
           

連結清單和遞歸

首先來看一個Leecode的問題,203,删除連結清單節點,這個問題很簡單:

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

删除一個連結清單裡面所有的節點。删除連結清單節點有兩種,一種是用虛拟頭結點删除,這種删除方法可以使得每一個節點的删除都可以一視同仁,一種算法可以搞定,但是如果不用虛拟頭結點,就要把第一個節點提出來單獨處理。這裡使用的是沒有虛拟頭節點的方法。

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        while (head != null && head.val == val){
            ListNode delNode = head;
            head = head.next;
            delNode.next = null;
        }

        if (head == null){
            return null;
        }

        ListNode pre = head;
        while (pre.next != null){
            if (pre.next.val == val){
                ListNode delNode = pre.next;
                pre.next = delNode.next;
                delNode.next = null;
            }else {
                pre = pre.next;
            }
        }
        return head;
    }
}
           

回到遞歸,遞歸本質上就是把一個原來的問題轉化成更小的同一個問題。通常這個·問題的規模是小到不能再小為止,這樣最後再往回退回去。而連結清單有天然的遞歸性,每一個節點後面都挂着另一個節點,是以可以看成是:

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

既然連結清單有遞歸性,那麼用遞歸來解決前面的問題。

每一次遞歸隻是處理目前頭部的節點是不是,如果是,那就不用傳回頭節點了,直接用下一層遞歸的結果傳回即可。這種方法寫起來非常簡單,不用考慮遞歸内部的運作流程,寫起來很抽象:

public ListNode removeElements_version2(ListNode head, int val){
        if (head == null){
            return null;
        }
        ListNode res = removeElements(head.next, val);
        if (head.val == val){
            return res;
        }else {
            head.next = res;
            return head;
        }
    }
           

運作結果是一樣的,但是更容易了解。

哈夫曼

哈夫曼編碼是一種統計編碼,屬于無損壓縮。基本思路就是對于每一個字元編碼的碼長是變化的,對于出現頻率高的,編碼長度會比較短,而對于出現頻率較低的,編碼長度較長。編碼規則還規定了每一個編碼都不能是其他字元編碼的字首。哈夫曼編碼是基于二叉樹的一種樹。

哈夫曼樹又稱為最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有葉子節點的權值乘上其到根節點的路徑長度。樹的帶權路徑長度為

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

,要做到權值越小,必須要保證權值越大的越靠近根節點,越小的越遠離。是以構造方法就出來了:1.給定n個權值,構成n棵隻有根節點的二叉樹,可以按照權值升序排列。2.在這些二叉樹裡面選擇權值最小的兩棵樹作為構造的二叉樹的新左右節點,這棵二叉樹的權值就是兩顆樹之和。删除兩棵樹,重複上述步驟直到隻有一棵樹。

對于構造的這棵哈夫曼樹,可以對他實作壓縮和解壓操作。首先是統計文本出現單詞的次數,把次數作為這個單詞出現的權值,然後按照上面的方法構造哈夫曼樹,把左邊的作為0,右邊的作為1,編碼輸出就是壓縮的資料。一般的操作流程是:先把用資料建立霍夫曼樹,然後按照左0右1的原則建立編碼,然後替換文本的編碼,把文本的編碼替換成0101的格式,也就是二進制,這樣就是可以使得整個檔案變成二進制,再8位8位的分開就可以壓縮成位元組了。

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

霍夫曼樹實作壓縮解壓

實作壓縮也就是幾個步驟:首先要統計詞頻,也就是統計每一個詞出現的數量,然後建立霍夫曼樹,得到霍夫曼編碼,變成位元組,輸出到壓縮檔案中。

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

函數的各個方法基本上就按照上述步驟實作。霍夫曼樹的實作需要一個優先隊列。因為每一次需要挑選最小的兩個組合成一顆新的二叉樹。statistic是統計函數,用于統計每一個字元出現的次數,做成map傳回。

private HuffmanPriorityQueue statistics(String str) {
        Map<Character, Integer> map = new HashMap<>();
        char[] charString = str.toCharArray();
        for (char c : charString) {
            Object object = map.get(c);
            if (object == null) {
                map.put(c, 1);
            } else {
                map.put(c, ((Integer) object) + 1);
            }
        }
        HuffmanPriorityQueue huffmanPriorityQueue = new HuffmanPriorityQueue(map.size());
        for (char c : map.keySet()) {
            HuffmanNode node = new HuffmanNode(c, map.get(c));
            huffmanPriorityQueue.insert(node);
        }
        return huffmanPriorityQueue;
    }
           

建立霍夫曼樹,首先是節點,霍夫曼樹的節點需要左右子節點和代表字元:

public class HuffmanNode {
    private char c;
    private int count;
    private HuffmanNode leftNode;
    private HuffmanNode rightNode;

    public HuffmanNode(char c, int count){
        this.c = c;
        this.count = count;
    }
           

建立霍夫曼樹的過程很簡單,每一次從優先隊列裡面出兩個最小值,構造一個新的節點,放回優先隊列,循環上述操作。

private HuffmanNode buildHuffmanTree(HuffmanPriorityQueue huffmanPriorityQueue) {
        while (huffmanPriorityQueue.size() > 1) {
            HuffmanNode left = huffmanPriorityQueue.remove();
            HuffmanNode right = huffmanPriorityQueue.remove();
            HuffmanNode root = new HuffmanNode((char) 0, left.getCount() + right.getCount());
            root.setLeftNode(left);
            root.setRightNode(right);
            huffmanPriorityQueue.insert(root);
        }
        return huffmanPriorityQueue.peekFront();
    }
           

然後就是建立霍夫曼編碼的過程,通過遞歸構造即可,遞歸到左邊子樹添0,右邊子樹添1。

private void buildHuffmanCode(Map<String, String> map, HuffmanNode huffmanTree, String stringCode) {
        if (huffmanTree.getLeftNode() == null && huffmanTree.getRightNode() == null) {
            map.put("" + huffmanTree.getC(), stringCode);
            return;
        }
        if (huffmanTree.getLeftNode() != null) {
            buildHuffmanCode(map, huffmanTree.getLeftNode(), stringCode + "0");
        }
        if (huffmanTree.getRightNode() != null) {
            buildHuffmanCode(map, huffmanTree.getRightNode(), stringCode + "1");
        }
    }
           

因為隻有節點才是字元,其他都是相加的中間節點而已。遞歸到葉子就停下。輸出到檔案函數:

private void outData(String str, Map<String, String> map, String outName) {
        File outFile = new File(outName);
        DataOutputStream dataOutputStream = null;
        try {
            dataOutputStream = new DataOutputStream(new FileOutputStream(outFile));
            outCode(dataOutputStream, map);
            String dataHuffmanCode = source2Code(str, map);
            outDataHuffmanCode(dataOutputStream, dataHuffmanCode);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                dataOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
           

首先要把編碼輸入檔案,告訴别人編碼是多少,長度是多少,然後再輸入内容。轉成了二進制字元之後,就需要八位八位的切分開來。然後把八位變成位元組即可:

private byte char2bytes(String s) {
        byte ret = 0;
        char[] cs = s.toCharArray();
        for (int i = 0; i < cs.length; i++) {
            byte tempB = (byte) (Byte.parseByte("" + cs[i]) * Math.pow(2, cs.length - i - 1));
            ret = (byte) (ret + tempB);
        }
        return ret;

    }
           

這樣就把八位變成了一個位元組,傳回即可。在切分的過程中,如果是可以被整除的,那就直接切分一個一個變成自己即可。如果是不能整除的,那就最後一位補上0,自己數組的最後一位就是儲存補了多少個0的數量。

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

最後壓縮成了位元組檔案。

解壓就簡單了,和上面相反操作即可:

public String decompress(String fileName) {
        DataInputStream in = null;
        String srcContent = "";
        try {
            in = new DataInputStream(new FileInputStream(new File(fileName)));
            Map<String, String> map = readCode(in);
            byte[] data = readDatas(in);
            int[] dataInt = bytes2intArray(data);
            srcContent = Huffman2Char(map, dataInt);
            System.out.println(srcContent);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return srcContent;
           

根據第一行讀出來的還原即可。但是要注意在後面位元組變成二進制字元的時候有可能存在不少八位的問題,有可能是4 5 6這樣的,這個時候就要在左邊補上0了,補齊8位,因為是按照8位分的。

Data Structure_數組_棧_隊列_連結清單_霍夫曼數組棧隊列連結清單哈夫曼

成功還原。

最後附上github位址: https://github.com/GreenArrow2017/DataStructure/tree/master/DataStucture