算法第四版學習(chapter1.3)

前幾天,回了老家,怠惰了幾天,把雲圖看了,還看了一季的King of the hill ,簡直爽爆,回來了,又要開始學習了,今天我們來學習幾種極爲常見的數據結構,分別是揹包、隊列還有棧。

1.3.1:

知識點:

泛型:在java中,我們可以規定集合類型的一個關鍵屬性,像這樣(Bag<pig> bag=new Bag<pig>),那麼這個包就只能往裏放pig類型的對象,當你往裏面放入其他類型時,就會編譯報錯,能自動裝箱轉換的類型除外。

自動裝箱:由於泛型只能設置爲引用類型的數據,當我們想限制集合只能是某原始數據類型,如int,float,double之類的,我們只能先將它們變成對應的引用類型,如Int,Float,Double,這樣就能成爲泛型,當我們設置了這些引用類型時,我們往集合存數據,就會自動裝箱,將該原始數據類型轉成他的引用類型,簡稱自動裝箱。

下面就進入數據結構的學習:

揹包:

揹包是一種不支持刪除元素的集合類型,主要用於收集元素,迭代遍歷所有元素。光進不出,想看可以,拿走?不可能。

隊列:

隊列遵循先進先出,先進隊列的,能先提取出來,就像排隊一樣,不多說了。

棧:

棧的話遵循後進先出,先進去的被壓在底下,後進去的在面上,就想做蛋糕,最後放的那層水果最先吃,蛋糕胚子最後。

講個例子:算術運算符壓棧之後怎麼處理?在p81其實講的非常清晰,整條運算式子從左往右掃描,左括號忽略,數值和運算符分別壓棧,當出現右括號時,數值棧出棧兩個,運算符棧出棧一個,運算結果入棧,直到掃描完成,得到最終結果。

1.3.2:

定容棧:即在實例化的時候,設定好了棧的大小,超出棧大小的時候,不會再往裏壓數據。

迭代器的api:hasnext,檢查棧內元素個數是否大於1,next將一個元素出棧。

動態調整數組的棧:由於棧用數組實現,數通組有固定大小,當棧大小達到上線時,可以更換更大的數組,然後將數據轉移,更換引用對象,從而動態調整棧的大小。

1.3.3:

鏈表:

鏈表並不是java直接支持的一種數據結構,C++上用鏈表方便多了,在java上鍊表,我們就需要自己寫鏈表的類,設置它的API,我們需要時根據不同的鏈表,設計不同的節點(當單向鏈表時,我們設計node的時候,我們只需要設計一個指向下一個node的成員變量,還有一個成員變量要指向一個自己的元素,雙向鏈表的話就還要設計一個指向上一個節點的成員變量)

鏈表的操作:①表頭插入節點,將表頭的next指向該節點,該節點的next指向原來的首節點。

                     ②表頭刪除節點,將表頭指向首節點.next,結束。

                     ③表尾插入節點,這個就比較難處理了,我們需要創建一個引用指向末節點,當我們需要在表尾添加節點時,用該引用找到末節點,設置末節點的next,而且修改對末節點的引用變量的引用,改爲引用新加入的節點。

                     ④其他節點的插入,想插在那個節點後面則,先將要插入節點的next設置爲插入位置的next對應的節點,再講插入的位置的next設置爲要插入節點。

接下來就是做題部分了,這個章節的題目好像有點多哦,開工

1.3.1 我們看回p82,定容棧是用數組實現的,我們要想知道,棧是否滿,我們只需要比較一下size是否等於棧的定容就好了,不給代碼了,大概知道知道意思就好了。

1.3.2 沒什麼好說的

1.3.3 對於這個問題,我們能憑藉自己的大腦記憶來判斷棧出入是否正確,但是若果出入棧的數字變多了呢,100你能記憶嗎?

所以我們要從中找到其規律,以下是我參照網絡之後的思路,我們將入棧序列和出棧序列同時入棧,然後講一個引用指向各自的棧頂,當引用的數字相同時出棧,不一樣時,入棧的引用往後移,若最後兩個棧都空了,則序列合法,兩個棧不爲空則不合法,我還是我代碼整出來吧。

  public boolean isLegal(int[] in, int[] out) {
        if (in.length != out.length || in.length == 0) {
            return false;
        }
        //輸入的棧
        LinkedList<Integer> ins=new LinkedList<Integer>();
        int j=0;
        for (int i = 0; i < in.length; i++) {
            //輸入壓棧
                ins.push(in[i]);
                //然後在輸出上找當前棧頂是否匹配當前輸出位置
                while(ins.size()>0&&ins.peek()==out[j]){
                    ins.pop();
                    j++;
                }
        }
        return ins.size()>0?false:true;
    }

      1.3.4 這個問題跟前面提到的算術式的壓棧其實非常類似的,我就直接上代碼吧              

 public boolean Parenthess(String s){
        char[] chars = s.toCharArray();
        LinkedList<Character> inS=new LinkedList<Character>();
        //爲了防止空棧直接進一個右邊括號,peek的時候出錯
        inS.push(' ');
        for(int i=0;i<chars.length;i++){
            //若是右邊部分,就進行配對,配上了就出棧,配不上就絕對是不合法的
            if(chars[i]==')'){
                if(inS.peek()=='('){
                    inS.pop();
                    continue;
                }else {
                    return false;
                }
            }
            if(chars[i]==']'){
                if(inS.peek()=='['){
                    inS.pop();
                    continue;
                }else {
                    return false;
                }
            }
            if(chars[i]=='}'){
                if(inS.peek()=='{'){
                    inS.pop();
                    continue;
                }else {
                    return false;
                }
            }
            //若不是右邊部分就正常壓棧
            inS.push(chars[i]);
        }
        //最後看看有沒有沒被匹配出棧的左邊的括號,有就返回錯,無則返回true
        if(inS.size()>1){
            return false;
        }else {
            return true;
        }

    }

1.3.7 peek:stack上有一個first棧頭,返回first.item就好了。

1.3.9 補全左括號,跟那個運算式子有點關係,直接上代碼吧

public void becomeWhole(String s){
        String[] strings = s.split("");
        Stack<String> whole=new Stack<String>();
        Stack<String> operaters=new Stack<String>();
        //正則,用來匹配數字和運算符
        String pattern="[0-9]";
        String pattern1="(\\+|\\-|\\*|\\/)";
        Pattern p=Pattern.compile(pattern);
        Pattern p1=Pattern.compile(pattern1);
        Matcher matcher;
        Matcher matcher1;
        for(int i=0;i<strings.length;i++){
            //如果是0-9的話壓進全棧
            //如果是+-*/就壓進運算符棧
            matcher=p.matcher(strings[i]);
            matcher1=p1.matcher(strings[i]);
            if(matcher1.find()){
                operaters.push(strings[i]);
                continue;
            }
            if(matcher.find()){
                whole.push(strings[i]);
                continue;
            }
            //如果是),whole彈兩個,運算符彈一個,加上括號組成真正的運算式
            if(strings[i].equals(")")){
                String s1=whole.pop();
                String s2=whole.pop();
                String o1=operaters.pop();
                whole.push("("+s2+o1+s1+")");
            }

        }
        System.out.println(whole.pop());

    }

1.3.10 中序換成後序,我們首先思考一個問題,什麼是前序,中序,後序,這就跟我們運算值和運算符的擺放位置有關係了。

          我一般都用的是中序,例如1+2,運算符在運算值中間,前序則是+12,後續則是12+,好吧,其實超簡單的,講我上面的代碼稍微改一下就好了。

1.3.11 跟運算式入棧基本無差,好吧

public void EvaluatePostfix(String s){
        String[] strings = s.split("");
        Stack<Integer> whole=new Stack<Integer>();
        Stack<String> operaters=new Stack<String>();
        String pattern="[0-9]";
        String pattern1="(\\+|\\-|\\*|\\/)";
        Pattern p=Pattern.compile(pattern);
        Pattern p1=Pattern.compile(pattern1);
        Matcher matcher;
        Matcher matcher1;
        for(int i=0;i<strings.length;i++){
            //如果是0-9的話壓進全棧
            //如果是+-*/就壓進運算符棧
            matcher=p.matcher(strings[i]);
            matcher1=p1.matcher(strings[i]);
            if(matcher1.find()){
                operaters.push(strings[i]);
                continue;
            }
            if(matcher.find()){
                whole.push(Integer.parseInt(strings[i]));
                continue;
            }
            //如果是),whole彈兩個,運算符彈一個,加上括號組成真正的運算式
            if(strings[i].equals(")")){
                int n1=whole.pop();
                int n2=whole.pop();
                String o1=operaters.pop();
                if(o1.equals("+")){
                    whole.push(n2+n1);
                }
                if(o1.equals("-")){
                    whole.push(n2-n1);
                }
                if(o1.equals("*")){
                    whole.push(n2*n1);
                }

            }

        }
        System.out.println(whole.pop());
    }

1.3.12 利用迭代器,將數據取出,在塞進副本,完成在傳出來

public Stack<String> copy(Stack<String> s){
        Stack<String> copy=new Stack<>();
        Iterator<String> iterator=s.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            System.out.print(next);
            copy.push(next);
        }
        return copy;
    }

1.3.13 看1.3.3吧。

1.3.14 直接上代碼吧,因爲是數組實現的數組,所以節點就不需要next了

public class ResizingArrayQueueOfStrings {
    class Node{
        String s;


        public Node(String s) {
            this.s = s;

        }

        public String getS() {
            return s;
        }

        public void setS(String s) {
            this.s = s;
        }

    }
    int size;
    //隊頭和隊尾
    int f=0;
    int l=0;

    public ResizingArrayQueueOfStrings(int size) {
        this.size = size;
        queue=new Node[size];
    }

    private Node[] queue;
    //進隊列
    public void push(String s){
        if((l)==size){
            resizing();
        }
        Node n=new Node(s);
        queue[l]=n;
        l++;
    }
    //出隊列
    public void pop(){
        System.out.println(queue[f]);
        f++;
    }
    //每次擴容將容量X2,引用新的數組
    private void resizing(){
        size=size*2;
        Node[] noeq=new Node[size];
        for(int i=0;i<queue.length;i++){
            noeq[i]=queue[i];
        }
        queue=noeq;
    }

}

1.3.15 毫無技術難度,好吧

public String getdesc(int n){
        if(n>(l-f)){
            System.out.println("無該字符");
            return "error";
        }else {
            return queue[l-n].s;
        }
    }

1.3.16 em。。。。

public class ResizingArrayQueueOfDates {

    private Date[] queue;
    int size;
    //隊頭和隊尾
    int f=0;
    int l=0;
    public ResizingArrayQueueOfDates(int size) {
        this.size = size;
        queue=new Date[size];
    }
    class Date {
        int year;
        int month;
        int day;

        public Date(String s) {
            String[] datepart = s.split("/");
            this.year = Integer.parseInt(datepart[0]);
            this.month = Integer.parseInt(datepart[1]);
            this.day = Integer.parseInt(datepart[2]);
        }
    }

    public void input(String s){
        String[] dates=s.split(" ");
        for(String d:dates){
            if((l)==size){
                resizing();
            }
            Date date=new Date(d);
            queue[l]=date;
            l++;
            }

        }

        //每次擴容將容量X2,引用新的數組
        private void resizing(){
            size=size*2;
            Date[] noeq=new Date[size];
            for(int i=0;i<queue.length;i++){
                noeq[i]=queue[i];
            }
            queue=noeq;
        }

}

1.3.17 跟上面那題基本一樣的思路就不整了

1.3.19

public void deLinkedListL(){
        Node find=first;
        //找鏈表last節點的前一個節點
        while(find.next!=last){
            find=find.next;
        }
        //將其的last設爲空,將其設爲last
        find.next=null;
        last=find;
        System.out.println("刪除表尾節點完成");
    }

1.3.20 直接看代碼吧

 public void deleteN(int n){
        Node del=first;
        //比如我要刪除第五個,那我就要先讀到第四個,然後將第四個的後面隔一個賦值到第四個的next上,那就可以吧第五個跳過了,
        // 然後GC就把第五個收走了,因爲鏈表從1開始,那就到一就刪。
        while(n>1){
            if(del.next!=null){
                del=del.next;
                n--;
            }else{
                System.out.println("節點不足");
                return;
            }

        }
        System.out.println(del.i);
        del.next=del.next.next;
    }

1.3.21 直接往下遍歷,有就返回true,若是最後都沒有找到就返回false

public boolean find(int key){
        Node find=first;
        while (find.next!=null){
            if(find.i==key){
                return true;
            }
            find=find.next;
        }
        return false;
    }

1.3.24 這個用我上面寫的那個deleteN就能實現,n+1就好了,他說的拿個無需操作,那就忽視的我的sout吧。

1.3.25 先遍歷,找到之後,保存該節點的next,將新增節點插進去後,新增節點的next設爲保存的節點

public void insertAfter(int key,int node){
        Node find=first;
        while (find.next!=null){
            if(find.i==key){
                Node temp=find.next;
                find.next=new Node(node);
                find.next.next=temp;
                return;
            }
            find=find.next;
        }

    }

1.3.26 遍歷,查看自己的next時候符合key,是的話就刪,不是繼續走

public void remove(int key){
        Node find=first;
        while(find.next!=null){
            if(find.next.i==key){
                find.next=find.next.next;
            }
            find=find.next;
        }
    }

1.3.27 遍歷,然後逐一比較,冒泡一遍求最大

public void getMax(){
        Node find=first;
        int max=find.i;
        while (find.next!=null){
            if(find.next.i>max){
                max=find.next.i;
            }
            find=find.next;
        }
        System.out.println(max);
    }

1.3.28 遞歸手法求出求上一問

public int getMax(Node n,int max){
        if(n.i>max){
            max=n.i;
        }
        if(n.next==null){

            return max;
        }
        return getMax(n.next,max);
    }

    public void go(){
        System.out.println(getMax(first,first.i));
    }

1.3.29 將last的next設成first就好了,沒什麼技術難度。

1.3.31 這個將node設置成雙向的就好了,一個next指向下一個節點,一個last指向上一個節點。

1.3.32 Steque

public class Steque {
    class Node{
        int i;
        Node next;

        public Node(int i) {
            this.i = i;
        }
    }
    Node first;
    Node last;
    public Steque() {
        Node n=new Node(0);
        first=n;
        last=n;
    }
    public Node pop(){
        //將first傳給first.next,然後把first傳進去
        if(first!=null){
            Node p=first;
            first=first.next;
            return p;
        }else {
            System.out.println("鏈表爲空");
            return null;
        }

    }
    public void push(int i){
        //將新的節點的next設成原來的first節點,然後將新的節點設成first節點
        Node n=new Node(i);
        n.next=first;
        first=n;
    }

    public void enqueue(int i){
        //把之前的尾節點的next賦值新進的節點,再把新節點設成尾節點
        Node n=new Node(i);
        last.next=n;
        last=n;
    }

}

1.3.33Deque的api,用數組太蠢了,每次表頭插入,得整個數組移動一位,表頭刪除又要數組向前一位,我用的直接引用的形式

public class Deque {
    Node first;
    Node end;
    class Node{
        int i;
        Node next;
        Node last;

        public Node(int i) {
            this.i = i;
        }

    }

    public void push(int i){
        Node n=new Node(i);
        if(first==null){
            first=n;
            end=n;
        }else {
            first.last=n;
            n.next=first;
            first=n;
        }
        show();
    }

    public Node pop(){
        if(first==null){
            System.out.println("隊列沒東西,別鬧了");
            return null;
        }else {
            Node n=first;
            first.next.last=null;
            first=first.next;
            show();
            return n;
        }

    }

    public void pushEnd(int i){
        Node n=new Node(i);
        if(first==null){
            first=n;
            end=n;
        }else {
            end.next=n;
            n.last=end;
            end=n;
            show();
        }


    }

    public Node popEnd(){
        if(first==null){
            System.out.println("隊列沒東西,別鬧了");
            return null;
        }
        Node n=end;
        end.last.next=null;
        end=end.last;
        show();
        return n;
    }

    void show(){
        Node n=first;
        do {
            System.out.print("\t"+n.i);
            n=n.next;
        }while(n!=null);
        System.out.println("\t"+"f---"+first.i+"\t"+"e---"+end.i);
    }
}

1.3.34 隨機揹包,在迭代器裏將其亂序展示

public class RandomBag {
    int[] bag=new int[100];
    int size;
    class Iterator{
        int[] rBag;
        int size;
        public Iterator(int[] bag,int size) {
            this.size=size;
            Random random=new Random();
            rBag=new int[size];
            for(int i=0;i<size;i++){
                rBag[i]=bag[i];
            }
            int temp=0;
            for(int i=0;i<size;i++){
                int r = random.nextInt(size);
                temp=rBag[i];
                rBag[i]=rBag[r];
                rBag[r]=temp;
            }
        }

        public void show(){
            for(int i=0;i<size;i++){
                System.out.print("\t"+rBag[i]);
            }
            System.out.println();
        }
    }
    public boolean idEmpty(){
        if(bag[0]==0){
            return false;
        }else {
            return true;
        }
    }

    public int size(){
        return size;
    }

    public void add(int i){
        bag[size]=i;
        size++;
    }

    public void iteratorShow(){
        new Iterator(bag,size).show();
    }
}

1.3.35 隨機隊列

package chapter1_3;

import java.util.Random;

public class RandomQueue {
    Card[] cards;
    int size;
    int last;

    public RandomQueue(int size) {
        this.size = size;
        cards=new Card[size];
        last=0;
    }

    public void resizing(int size){
        this.size=size;
        Card[] temp=new Card[size];
        for(int i=0;i<last;i++){
                temp[i]=cards[i];
        }
        cards=temp;
    }

    public void enqueue(String num, String huase){
        Card c=new Card(num,huase);
        //若果尾巴也有數據就擴張
        if(cards[size-1]!=null){
            resizing(size*2);
        }
        cards[last]=c;
        last++;
    }

    public Card dequeue(){
        Card c=cards[0];
        last--;
        for(int i=0;i<last;i++){
            cards[i]=cards[i+1];
        }
        //如果中間也是空的就收縮
        if(cards[(size/2)-1]==null){
            resizing(size/2);
        }
        //抽卡的時候觸發置換,用末尾的話,初期發牌太有規律了,根本沒有起到洗牌的功能,所以我換成了頭部置換
        Random random=new Random();
        int r = random.nextInt(last);
        Card temp=cards[r];
        cards[r]=cards[0];
        cards[0]=temp;
//        System.out.println("r"+cards[r]);
//        System.out.println("l"+cards[0]);
        return c;
    }

    public Card sqmple(){
        Random random=new Random();
        int r = random.nextInt(last);
        return cards[r];
    }

    public void start(){
        String num;
        String huase="";
        for(int i=0;i<4;i++){
            if(i==0){
                huase="方塊";
            }
            if(i==1){
                huase="梅花";
            }
            if(i==2){
                huase="紅心";
            }
            if(i==3){
                huase="黑桃";
            }

            for(int j=1;j<14;j++){
                if(j==11){
                    num="J";
                }else if(j==12){
                    num="Q";
                }else if(j==13){
                    num="K";
                }else {
                    num=""+j;
                }
                enqueue(num,huase);
            }
        }

    }
}

1.3.36 這個我就不寫了,沒多大意思,數組遍歷唄

1.3.37 經典題目:約瑟夫環,我打算用一個環形的鏈表解決這個問題

public class Josephus {
    Node start;
    Node last;
    int kill;

    class Node{
        int name;
        Node next;

        public Node(int name) {
            this.name = name;
        }
    }

    public Josephus(int size,int kill) {
        this.kill=kill;
        for(int i=0;i<size;i++){
            enqueue(i+1);
        }
    }

    public void enqueue(int name){
        Node n=new Node(name);
        //當前鏈表爲空的時候,將該節點設爲start
        if(start==null){
            start=n;
            last=n;
            n.next=n;
        }else{
            last.next=n;
            n.next=start;
            last=n;
        }
    }

    public void startKill(){
        int k;
        Node getKilled=last;
        while(true){
            k=kill;
            while (k>1){
                //按着開槍間隔往後走
                getKilled=getKilled.next;
                k--;

            }
            System.out.println(getKilled.next.name);
            if(getKilled.next==getKilled){
                System.out.println("結束");
                break;
            }
            //把要死的節點的前面的節點的next設爲死的節點的next,節點死掉
            getKilled.next=getKilled.next.next;
        }
    }
}

1.3.38 說下思路吧,就不寫代碼了,先是鏈表,有個int類型標明進來過多少個節點,每個節點進入的時候都會有個int類型的數據,上面標明該節點是第幾進來的,關於這個delete方法,獲取了k之後,直接搜索隊列,int數據類型=k,直接拿出來。

1.3.39 環形緩存器

public class RingBuffer {
    Node start;
    Node last;
    int capacity;
    int now;
    class Node{
        public Node(int name) {
            this.name = name;
        }

        Node next;
        int name;
    }

    public RingBuffer(int capacity) {
        this.capacity = capacity;
        now=0;
    }

    void enqueue(int name){
        Node n=new Node(name);
        if(start==null){
            start=n;
            last=n;
            n.next=n;
            now++;
        }else {
            last.next=n;
            n.next=start;
            last=n;
            now++;
        }
        if(now==capacity){
            consume();
        }
    }

    public void product(int i){
        System.out.println("生產"+i+"個");
        while(i-->0){
            enqueue(now);
        }
    }

    void consume(){
        Node n=start;
        System.out.println("啓動緩存消耗");
        for(int i=0;i<capacity;i++){
            System.out.print(n.name);
            n=n.next;
        }
        System.out.println();
        start=null;
        last=null;
    }
}

1.3.40 前移編碼,又是一個非常簡單的,不上代碼了,每次輸入的時候,進行鏈表節點逐個校對,無就存,有就刪掉,從頭的部分重新進入一次,以前做的find功能修改一下就可以用了。

1.3.41和1.3.42基本一致思路,就是將需要被複制的隊列或者棧,通過構造器,遍歷複製一遍就好了。

1.3.43 文件列表

public class FileList {
    File[] files;
    int p=0;
    int maxl;
    class File{
        String name;
        int level;

        public File(String name, int level) {
            this.name = name;
            this.level = level;
        }
    }

    public FileList() {
        files=new File[100];
        this.maxl=0;
    }

    public void search(String filePath,int level){
        if(level>maxl){
            maxl=level;
        }
        java.io.File f=new java.io.File(filePath);
        java.io.File[] list = f.listFiles();
        for (java.io.File s:list
             ) {
            files[p]=new File(s.getName(),level);
            p++;
        }

        for(java.io.File s:list){
            if (s.isDirectory()){
                search(s.getPath(),level+1);
            }
        }
    }

    public void show() {
        int layer = maxl+1;
        while (layer-- > 0) {
            for (int i = 0; i < p; i++) {
                if (files[i].level == maxl - layer) {
                    System.out.print("\t" + files[i].name+"\t");
                }
            }
            System.out.println();
        }

    }
}

1.3.44 文本編輯緩衝區,說下思路吧,兩個棧來進行存儲,一個棧是光標左邊的字符,另一個棧是光標右邊的字符,光標往左移則右邊的棧出棧,出棧的字符入棧右邊,往右邊動則blabla,刪除的話,就左邊出棧,然後輸出出來,插入字符則是往左邊的棧進棧。

1.3.45 設定一個整型變量,進棧+1,出棧-1,一直往後度,整型變量一小於一,就返回報錯。

1.3.46 入棧問題,入棧的次序決定了出棧的先後,不管怎麼進其他的數據都一樣

1.3.47 連接隊列,一個方法獲取兩個隊列,將第二個隊列的數據出列,入列第二個隊列,最後得到一個鏈接完成的隊列

          連接棧的話就麻煩點,其中一個棧的全部提取出來存在數組裏,在逆序壓入另一個棧

1.3.48 雙向隊列裏一開始有一個節點叫做bottom,左邊壓一個棧,右邊壓一個,取到碰到bottom則是棧已取空

1.3.49 嗯。。。。兩個棧,現將隊列所有數據壓入一棧,再出棧壓入二棧,要出列的話,則直接二棧出棧,要入隊列的話,就先將二棧所有出棧,入棧一棧,需要入隊列的的數據直接壓入一棧

1.3.50 一種樂觀鎖的手法,認爲一般不會有人改我的數據,但是上鎖被改過之後,就會報異常

 

 

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