算法第四版学习(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 一种乐观锁的手法,认为一般不会有人改我的数据,但是上锁被改过之后,就会报异常

发布了8 篇原创文章 · 获赞 2 · 访问量 1628

猜你喜欢

转载自blog.csdn.net/z1261203654/article/details/81569138