Java常見數據結構整理(數組,棧,隊列,鏈表,紅黑樹)一

目錄

 

什麼是數據結構?

爲什麼我們需要數據結構?

1.數組

1.1數組基本操作

1.2數組常見面試算法

1.2.1尋找數組中第二小的元素

1.2.2找到數組中第一個不重複出現的整數

1.2.4重新排列數組中的正值和負值

2.棧(後進先出)

2.1棧的基本操作

2.2數組常見面試算法

2.2.1用兩個棧來實現一個隊列

2.2.3使用棧計算後綴表達式

2.2.4判斷表達式是否括號平衡

3.隊列(先進先出)

3.1隊列是什麼 ?

3.2隊列的用途是什麼?

3.3隊列接口定義

3.4沒有實現阻塞隊列的接口

3.5實現阻塞隊列的接口

3.5.1添加元素


什麼是數據結構?

簡單地說,數據結構是以某種特定的佈局方式存儲數據的容器。這種“佈局方式”決定了數據結構對於某些操作是高效的,而對於其他操作則是低效的。首先我們需要理解各種數據結構,才能在處理實際問題時選取最合適的數據結構。

爲什麼我們需要數據結構?

數據是計算機科學當中最關鍵的實體,而數據結構則可以將數據以某種組織形式存儲,因此,數據結構的價值不言而喻。無論你以何種方式解決何種問題,你都需要處理數據——無論是涉及員工薪水、股票價格、購物清單,還是隻是簡單的電話簿問題。數據需要根據不同的場景,按照特定的格式進行存儲。有很多數據結構能夠滿足以不同格式存儲數據的需求。

1.數組

數組是最簡單、也是使用最廣泛的數據結構。棧、隊列等其他數據結構均由數組演變而來。下圖是一個包含元素(1,2,3和4)的簡單數組,數組長度爲4。

每個數據元素都關聯一個正數值,我們稱之爲索引,它表明數組中每個元素所在的位置。大部分語言將初始索引定義爲零。

數組分爲兩種類型:一維數組,多維數組;

1.1數組基本操作

1)Insert——在指定索引位置插入一個元素

2)Get——返回指定索引位置的元素

3)Delete——刪除指定索引位置的元素

4)Size——得到數組所有元素的數量

1.2數組常見面試算法

1.2.1尋找數組中第二小的元素

public void secondMax(int[] arrays){
        if(arrays.length == 0) return;
        int max = arrays[0];
        int secondMax = arrays[0];
        for(int i=1; i<arrays.length; i++){
            if(max<arrays[i]){
                secondMax = max;
                max = arrays[i];
            }
        }
        System.out.println(secondMax);
    }

1.2.2找到數組中第一個不重複出現的整數

public void firstNoRepeat(int[] arrays){
        //雙循環
        //遍歷所有元素
        for(int i=0; i<arrays.length; i++){
            int count = 1;
            //與比較arrays[i]元素比較是否相等
            for(int l=0; l<arrays.length; l++){
                if(arrays[i] == arrays[l] && i!=l){
                    count++;
                    break;//重複元素退出當前循環
                }
            }
            if(count == 1){//第一次出現不重複的元素
                System.out.println(arrays[i]);
                break;
            }
        }
        
        //藉助HashMap存儲元素,值做爲key
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i=0; i<arrays.length; i++){
            if(map.get(arrays[i]) == null){
                map.put(arrays[i], 1);
            }else {
                int value = map.get(arrays[i]);
                map.put(arrays[i], value+1);
            }
        }
        //判斷第一次出現一次的整數
        for(int i=0; i<arrays.length; i++){
            if(map.get(arrays[i]) == 1){
                map.get(arrays[i]);
                break;
            }
        }
    }

1.2.3合併兩個有序數組

int[] sortone = new int[]{1,30,30,100,101,345,346};
int[] sorttwo = new int[]{1,2,30,300,301,302,400,405,406};
mergeSort(sortone, sorttwo);
public void mergeSort(int[] sortone, int[] sorttwo){
        int one = 0;
        int two = 0;
        int[] merge = new int[sortone.length+sorttwo.length];
        int index = 0;
        while (one<sortone.length && two<sorttwo.length){
            if(sortone[one]<=sorttwo[two]){
                merge[index++] = sortone[one++];
            }else {
                merge[index++] = sorttwo[two++];
            }
        }

        while (one<sortone.length){
            merge[index++] = sortone[one++];
        }

        while (two<sorttwo.length){
            merge[index++] = sorttwo[two++];
        }
        System.out.println(merge);
    }

1.2.4重新排列數組中的正值和負值

將數組裏的負數排在數組的前面,正數排在數組的後面。但不改變原先負數和正數的排列順序;

int v[] = {-5, 2, -3, 4, -8, -9, 1, 3, -10};
	//int v[] = {-5, 2, -3, 4, -8, -9, 1, 3, 12, 15, 19, -7,-2,};
	//int v[] = {-1, -3, -5, -7, 1, 3, 5, 7, 9};

public void process(int[] negative){
        int[] negativecopy = new int[negative.length];
        int begin = 0;
        int end  = negative.length-1;
        for(int l=begin, r=end;  l<negative.length&&r>=0;l++, r--){
            if(negative[l]<0){
                negativecopy[begin++] = negative[l];
            }
            if(negative[r]>=0){
                negativecopy[end--] = negative[r];
            }
        }
        System.out.println(negativecopy);
    }

2.棧(後進先出)

著名的撤銷操作幾乎遍佈任意一個應用。但你有沒有思考過它是如何工作的呢?這個問題的解決思路是按照將最後的狀態排列在先的順序,在內存中存儲歷史工作狀態(當然,它會受限於一定的數量)。這沒辦法用數組實現。但有了棧,這就變得非常方便了。

可以把棧想象成一列垂直堆放的書。爲了拿到中間的書,你需要移除放置在這上面的所有書。這就是LIFO(後進先出)的工作原理。

下圖是包含三個數據元素(1,2和3)的棧,其中頂部的3將被最先移除:

2.1棧的基本操作

1)Push——在頂部插入一個元素

2)Pop——返回並移除棧頂元素

3)isEmpty——如果棧爲空,則返回true

4)Top——返回頂部元素,但並不移除它

2.2數組常見面試算法

2.2.1用兩個棧來實現一個隊列

用兩個棧來實現一個隊列,完成隊列的Push和Pop操作。 隊列中的元素爲int類型。

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) { //stack1入棧操作
        stack1.push(node);
    }
    
    public int pop() {//stack1模擬隊列先入先出
        while(!stack1.isEmpty()){
            int value = stack1.pop();
            stack2.push(value);
        }
        if(!stack2.isEmpty()){
            int result = stack2.pop();
            while(!stack2.isEmpty()){
                int value = stack2.pop();
                stack1.push(value);
            }
            return result;
        }
        return 0;
    }
}

2.2.2對棧的元素進行排序

將棧中的元素排序;

public void stackSort(Stack<Integer> stack){
        //臨時堆棧
        Stack<Integer> stacktwo = new Stack<>();
        //排序處理
        while (!stack.isEmpty()){
            int topone = stack.pop(); //獲取stack頭部元素
            int toptwo = -1;
            //判斷臨時堆棧元素元素是否小於stack頭部元素
            while (!stacktwo.isEmpty()&&(toptwo=stacktwo.peek())<topone){
                //將小於stack頭部元素重新壓入stack
                stack.push(stacktwo.pop());
            }
            //頭部元素壓入臨時棧
            stacktwo.push(topone);
        }
    }

2.2.3使用棧計算後綴表達式

package com.github.baby.owspace.view.activity.fragments;

import java.util.Scanner;
import java.util.Stack;
import java.util.StringTokenizer;

/**
 * 使用棧計算後綴表達式
 */

public class PostfixEvaluator {
    //定義操作符
    private final static char ADD  = '+';
    private final static char SUBTRACT  = '-';
    private final static char MULTIPLY = '*';
    private final static char DIVIDE = '/';
    //存放數據堆棧
    private Stack<Integer> stack;

    public PostfixEvaluator(){
        stack = new Stack<>();
    }
    //判斷是否是操作符
    public boolean isOperator(String str){
        return str.equals("+") || str.equals("-") ||
                str.equals("*") ||str.equals("/");
    }
    
    //根據操作符進行計算
    private int caculateSingleOp(char operator, int num1, int num2){
        int result = 0;
        switch (operator){
            case ADD:
                result = num1+num2;
                break;
            case SUBTRACT:
                result = num1-num2;
                break;
            case MULTIPLY:
                result = num1*num2;
                break;
            case DIVIDE:
                result = num1/num2;
                break;
        }

        return result;
    }
    
    //解析後綴表達式
    public int evaluate(String str){
        int num1, num2, result=0; //result是臨時結果
        String token = ""; //存放解析到的字符
        StringTokenizer tokenizer = new StringTokenizer(str);
        while (tokenizer.hasMoreTokens()){
            token = tokenizer.nextToken(); //字符
            if(isOperator(token)){    //判斷是否是計算字符
                num2 = (stack.pop()).intValue();//第一次出棧放在右側計算
                num1 = (stack.pop()).intValue();//第二次出棧放在右側計算
                //計算結果
                result = caculateSingleOp(token.charAt(0), num1, num2);
                stack.push(result);//重新入棧
            }else {
                stack.push(new Integer(Integer.parseInt(token)));//非計算字符重新入棧
            }
        }
        return result;
    }

}


PostfixEvaluator evaluator = new PostfixEvaluator();
        evaluator.evaluate("35 9 *");
測試數據:7 4 -3 * 1 5 + / *

2.2.4判斷表達式是否括號平衡

/*
 * 寫一段代碼,判斷包含括號 { [ ( ) ] } 的表達式是否合法
 * 考慮對應關係的符號總是挨着這樣的特殊性,通過棧數據結構判斷入棧的元素和棧頂元素關係完成有效性的判斷
 */
public boolean valid(String expression){
        //定義堆棧結構
        Stack<Character> stack = new Stack<>();
        Map<String, String> map = new HashMap<>();
        map.put("{","}");
        map.put("[","]");
        map.put("(",")");

        char[] exp = expression.toCharArray();
        for(int i=0; i<exp.length; i++){
            //開始字符壓棧
            if(exp[i] == '{' || exp[i] == '[' || exp[i] == '('){
                stack.push(exp[i]);
            }
            //如果結束字符則出棧,則判斷當前棧頂元素和該結束符號是否是對應關係
            if(exp[i] == '{' || exp[i] == '[' || exp[i] == '('){
                String c = String.valueOf(stack.pop());
                if(exp[i] != map.get(c).toCharArray()[0]){
                    return false;
                }
            }

        }
        return true;
    }
String s = "{}(([{}])){}{}";
System.out.println(valid(s));

3.隊列(先進先出)

3.1隊列是什麼 ?

與Stack的區別在於, Stack的刪除與添加都在隊尾進行, 而Queue刪除在隊頭, 添加在隊尾;

一個完美的隊列現實例子:售票亭排隊隊伍。如果有新人加入,他需要到隊尾去排隊,而非隊首——排在前面的人會先拿到票,然後離開隊伍。

3.2隊列的用途是什麼?

一般情況下,如果是一些及時消息的處理,並且處理時間很短的情況下是不需要使用隊列的,直接阻塞式的方法調用就可以了。但是,如果在消息處理的時候特別費時間,這個時候如果有新的消息來了,就只能處於阻塞狀態,造成用戶等待。這個時候在項目中引入隊列是十分有必要的。當我們接受到消息後,先把消息放到隊列中,然後再用新的線程進行處理,這個時候就不會有消息的阻塞了;

3.3隊列接口定義

Queue接口與List、Set同一級別,都是繼承了Collection接口,LinkedList實現了Deque接口;

package java.util;

public interface Queue<E> extends Collection<E> {
    boolean add(E var1);    

    boolean offer(E var1);

    E remove();

    E poll();

    E element();

    E peek();
}

a.add  增加一個元索,如果隊列已滿,則拋出一個IIIegaISlabEepeplian異常
b.offer 添加一個元素並返回true,如果隊列已滿,則返回false
c.remove   移除並返回隊列頭部的元素,如果隊列爲空,則拋出一個NoSuchElementException異常
d.poll         移除並返問隊列頭部的元素    如果隊列爲空,則返回null
e.element  返回隊列頭部的元素,如果隊列爲空,則拋出一個NoSuchElementException異常
f.peek       返回隊列頭部的元素,如果隊列爲空,則返回null

public interface BlockingQueue<E> extends Queue<E> {}

g.put         添加一個元素,如果隊列滿,則阻塞
h.take        移除並返回隊列頭部的元素,如果隊列爲空,則阻塞

3.4沒有實現阻塞隊列的接口

a.LinkedList: 實現了java.util.Deque
內置的不阻塞隊列: PriorityQueue 和 ConcurrentLinkedQueue實現java.util.AbstractQueue接口;
PriorityQueue 和 ConcurrentLinkedQueue 類在 Collection Framework 中加入兩個具體集合實現。 
b.PriorityQueue 類實質上維護了一個有序列表。加入到 Queue 中的元素根據它們的天然排序(通過其 java.util.Comparable 實現)或者根據傳遞給構造函數的 java.util.Comparator 實現來定位。
c.ConcurrentLinkedQueue 是基於鏈接節點的、線程安全的隊列。併發訪問不需要同步。因爲它在隊列的尾部添加元素並從頭部刪除它們,所以只要不需要知道隊列的大小,ConcurrentLinkedQueue 對公共集合的共享訪問就可以工作得很好。收集關於隊列大小的信息會很慢,需要遍歷隊列;

 public static void main(String[] args) {
        //BlockingQueueTest.testBasket();
        //add()和remove方法在失敗的時候會拋出異常(不推薦)
        Queue<String> queue = new LinkedList<String>();
        queue.add("a");
        queue.add("b");
        queue.add("c");
        queue.add("d");
        //輸出結果 a b c d
        for(String q: queue){
            System.out.println(q);
        }
        //返回對頭元素,並在隊列中刪除  a
        System.out.println("poll="+queue.poll());
        //輸出結果 b c d
        for(String q: queue){
            System.out.println(q);
        }
        //返回對頭元素  b
        System.out.println("element="+queue.element());
        //輸出結果 b c d
        for(String q: queue){
            System.out.println(q);
        }
        //返回對頭元素 b
        System.out.println("peek="+queue.peek());
        //輸出結果 b c d
        for(String q: queue){
            System.out.println(q);
        }
    }

3.5實現阻塞隊列的接口

java.util.concurrent 中加入了 BlockingQueue 接口和五個阻塞隊列類。它實質上就是一種帶有一點扭曲的 FIFO 數據結構。不是立即從隊列中添加或者刪除元素,線程執行操作阻塞,直到有空間或者元素可用。
五個隊列所提供的各有不同:
a.ArrayBlockingQueue :一個由數組支持的有界隊列。
b.LinkedBlockingQueue :一個由鏈接節點支持的可選有界隊列。
c.PriorityBlockingQueue :一個由優先級堆支持的無界優先級隊列。
d.DelayQueue :一個由優先級堆支持的、基於時間的調度隊列。
e.SynchronousQueue :一個利用 BlockingQueue 接口的簡單聚集(rendezvous)機制。

如何處理隊列阻塞,及防治數據不一致:

3.5.1添加元素

ArrayBlockingQueue

    /** 等候提取元素條件 */
    private final Condition notEmpty;

    /** 等候放入元素的條件 */
    private final Condition notFull;

 * 在尾部插入指定元素, 隊列滿了則調用線程等待await()
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

1)加鎖,ReentrantLock

加鎖保證了不會同時有多個生產者來生產元素,達到阻塞,否則數據錯亂;

2)判斷當前隊列裏的元素數count,和隊列容量capacity比較

判斷是否可以往裏面生產元素,notFull.await();提示生產者線程隊列已滿,阻塞所有生產操作線程;

3)數據入隊

   private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length) putIndex = 0;
        count++;
        notEmpty.signal();
    }

4)計數++

5)調用notEmpty喚醒消費者線程有數據,消費者線程進行處理;

6)釋放lock,其它線程可以繼續調用offer了

3.5.2提取元素

ArrayBlockingQueue

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

1.加鎖ReentrantLock

加鎖保證了不會同時有多個消費者來消費元素,達到阻塞,否則數據錯亂;

2)判斷當前隊列裏的元素數count==0,隊列空

判斷是否可以繼續消費元素,notEmpty.await();提示消費者隊列已空,阻塞所有消費操作線程;

private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length) takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

4)計數--

5)調用notFull.signal();喚醒生產者線程可以繼續生產;

6)釋放lock,其它線程可以繼續調用take()了

阻塞隊列實現步驟:

1)生產消費同步加鎖,進行阻塞

2)生產和消費完判斷隊列元素個數,喚醒對應的生產消費線程;

/**
 * 線程隊列管理
 */
public class ThreadPoolManager {

    private static final String TAG = ThreadPoolManager.class.getSimpleName();

    private static ThreadPoolManager instance = new ThreadPoolManager();

    public static ThreadPoolManager getInstance(){
        return instance;
    }

    /**
     * 線程池
     */
    private ThreadPoolExecutor threadPoolExecutor;
    /**
     * 請求隊列
     */
    private LinkedBlockingDeque<Future<?>> service = new LinkedBlockingDeque<>();

    //線程池拒絕執行以後,放入阻塞隊列
    private RejectedExecutionHandler handler = new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            try {
                service.put(new FutureTask<Object>(r, null));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    /**
     * 初始化
     */
    private ThreadPoolManager(){
        threadPoolExecutor  = new ThreadPoolExecutor(4, 10,
                10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), handler);

        threadPoolExecutor.execute(runnable);
    }

    /**
     * 消費者,不斷檢測線程隊列是否有線程需要執行
     */
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            while (true){
                FutureTask futureTask = null;

                try {
                    Log.e(TAG, "隊列中等待數量:"+service.size());
                    futureTask = (FutureTask)service.take();
                    Log.e(TAG, "線程池中線程的數量:"+threadPoolExecutor.getPoolSize());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(futureTask !=  null){
                    //消費需要執行的線程
                    threadPoolExecutor.execute(futureTask);
                }

            }
        }
    };

    /**
     * 執行線程任務,延時多少秒執行,相當於生產者,把需要執行的線程放入消息隊列
     * @param futureTask
     * @param delayed
     * @param <T>
     */
    public <T> void execute(final FutureTask<T> futureTask, Object delayed){
        if(futureTask != null){
            //延時執行
            if(delayed != null){
                Timer timer = new Timer();
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        try {
                            service.put(futureTask);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }, (long)delayed);
            }else {
                //不需要延遲處理
                try {
                    service.put(futureTask);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

參考:

https://www.runoob.com/java/data-queue.html

https://baijiahao.baidu.com/s?id=1609200503642486098&wfr=spider&for=pc

https://www.cnblogs.com/xdecode/p/9321848.html

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章