Java数据结构:线性表、栈、队列和优先队列

目录

一、集合:Collection接口

二、迭代器:Iterable接口

迭代器与for循环

1、iterator()方法:获取迭代器

2、forEach()方法:每个都操作

三、线性表:List接口

1、双向遍历:ListIterator接口

2、线性表具体类:ArrayList类和LinkedList类

四、Comparator接口

Comparable接口和Comparator接口之间的不同

一些排序和比较的编程设计

五、Collections类:执行集合和线性表中通用操作的静态方法

六、Vector类和Stcak类:向量类和栈类

七、队列

1、Queue接口:队列

 2、Deque接口:双端队列

3、PriorityQueue类:优先队列

八、栈类实操:表达式求值


Java中的数据结构通常称为Java集合框架


一、集合:Collection接口

Set -- 存储一组不重复的元素

List -- 有序元素集合

Stack -- 采用后进先出方式处理的对象(堆)

Queue -- 采用后进后出方式处理的对象(队列)

Collection接口是处理对象集合的根接口。接口衍伸出的抽象类和具体类如下,其中可以看到熟悉的ArrayList类:

接下来几乎都围绕这张图来学习。 


二、迭代器:Iterable接口

Collection接口继承自Iterable接口。迭代器的工作是遍历并选择序列中的对象,提供了一种“不必暴露内部细节”就能访问数据结构中各个元素的方法。

  • 迭代器与for循环

通常什么情况下使用迭代器呢?我们知道遍历一个线性表也可以用for循环,但是for循环需要一个具体的“叫停数字”。那么相比for循环而言,当我们不确定一个线性表中元素个数的时候,我们可以使用迭代器中的next来判断下一个元素是否存在

Iterable接口实现一个迭代器:

1、iterator()方法:获取迭代器

 可以使用iterator()方法从集合中获取迭代器。

public class TestIterator {
    public static void main(String[] args){
        Collection<String> collection = new ArrayList<>();
        collection.add("NNn");
        collection.add("AAn");
        collection.add("DDn");
        collection.add("MMm");

        Iterator<String> iterator = collection.iterator(); //获取集合对象的迭代器
        while(iterator.hasNext()){
            //返回下一个元素的大写形式(遍历)
            System.out.println(iterator.next().toUpperCase() + "");
        }
        System.out.println();
    }
}

/*
NNN
AAA
DDD
MMM
*/

其中语句:

Iterator<String> iterator = collection.iterator();

collection.iterator()获取了一个针对实例对象collection的迭代器。

2、forEach()方法:每个都操作

可以使用forEach()方法对集合中的每个元素执行一个操作:

collection.forEach(e -> System.out.print(e.toUpperCase() + " "));

三、线性表:List接口

前面我们用的ArrayList类其实还有个兄弟---LinkedList类,他俩都定义在List接口下。List接口增加了面向位置的操作,并且增加一个可以双向遍历线性表的迭代器:

其中实现双向遍历的方法listIterator()返回一个ListIterator接口的一个实例。

1、双向遍历:ListIterator接口

ListIterator接口继承自Iterator接口。 在Iterator接口中我们看到的方法都是对元素的下一位(顺序)操作,而ListIterator接口在继承了这些顺序操作的同时,增加了一系列对上一位(逆序)操作,也就刚好应对了上面所说的双向顺序遍历:

其中 add(元素) 用于将指定元素插入到线性表中:

  • next()返回非空 --> 插入next方法返回的元素之前
  • previous()返回非空 --> 插入previous方法返回的元素之后 

其中注意next方法为Iterator接口中的方法,但因为ListInterator接口继承自Iterator接口,所以ListIterator接口实例也可以调用该方法。 

2、线性表具体类:ArrayList类和LinkedList类

数组线性表类ArrayList类和链表类LinkedList类是实现List接口的两个具体类:

  • ArrayList类:数组存储元素(通过下标随机访问元素,选它)
  • LinkedList类:链表存储元素(要在线性表中插入/删除元素,选它)

上面那个选择标准是怎么来的呢?因为数组创建即固定了大小,而链表是可以动态增加减小的。所以如果我们需要增加或删除元素,对于数组而言如果元素个数超过了定义的大小,那么就只能再创建一个新数组,然后把原数组复制到新数组中,就没有动态链表高效。 

 对比两个类中的方法,可以看出链表类LinkedList中添加了一些对元素的插入和删除操作方法。

下面的程序创建一个元素是数字的数组线性表,并向里面添加元素。然后从该线性表中创建了一个链表,并向链表中删除和插入元素。最后使用迭代器顺序和逆序遍历打印线性表: 

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

public class TestArrayAndLinked {
    public static void main(String[] args) {
        //数组线性表
        List<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(1);
        arrayList.add(4);
        arrayList.add(0, 10);
        arrayList.add(3, 30);

        System.out.println("A list of integers in the array list: ");
        System.out.println(arrayList); // [10, 1, 2, 30, 3, 1, 4]

        //线性表链表结构
        LinkedList<Object> linkedList = new LinkedList<Object>(arrayList);
        linkedList.add(1, "red");
        linkedList.removeLast();
        linkedList.addFirst("green");

        System.out.println("Display the linked list forward: ");
        //迭代器遍历操作
        ListIterator<Object> listIterator = linkedList.listIterator();
        while(listIterator.hasNext()) { // 顺序遍历打印
            System.out.print(listIterator.next() + " "); // green 10 red 1 2 30 3 1 
        }

        System.out.println("Display the linked list backward: ");
        while(listIterator.hasPrevious()){ // 逆序遍历打印
            System.out.print(listIterator.previous() + " "); // 1 3 30 2 1 red 10 green 
        }
    }
}

四、Comparator接口

前面有学习使用Comparable接口重写CompareTo方法即可对元素进行比较。那么对于一些没有实现Comparable接口的类,我们可以让其实现Comarator接口,通过重写它的compare方法来实现比较。

public int compare(T element1, T element2)
/*
1 = 2返回0
1 > 2返回正值
1 < 2返回负值
*/

Comparator接口中包括compare方法和equals方法。

  • Comparable接口和Comparator接口之间的不同

  1. Comparable在java.lang包中,Comparator在java.util包中;
  2. 使用Comparable接口,则必须在类内部实现这个接口,并重写comparaTo()方法;
  3. 但是Comparator接口可以在类外部使用,通过将该接口的一个匿名类对象当做参数传递给Collections.sort()方法或者Arrays.sort()方法实现排序,这样我们就可以不用改变类本身的代码而实现对类对象进行排序。

也就是说:当类可以被修改时,我们使用Comparable接口;当你无法修改类时,Comparator接口派上用场。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

class Child {
    private Integer id;
    private Integer age;

    public Child() {
    }

    public Child(Integer id, Integer age) {
        this.id = id;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Child{" +
                "id=" + id +
                ", age=" + age +
                '}';
    }
}

public class TestComparator2 {
    public static void main(String[] args) {
        Child child1 = new Child(1, 14);
        Child child2 = new Child(2, 12);
        Child child3 = new Child(3, 10);

        List<Child> list = new ArrayList<>();
        list.add(child1);
        list.add(child2);
        list.add(child3);

        Collections.sort( list, new Comparator<Child>() { // 匿名类对象当做参数传递
            @Override  // 在类外重写compare方法
            public int compare(Child o1, Child o2) {
                return  o1.getAge() > o2.getAge() ? 1 : (o1.getAge() == o2.getAge() ? 0 : -1);
            }
        });

        list.stream().forEach(System.out::println);
    }
}

上面这段代码先是使用Child类创造实例对象,然后创造一个数组线性表,并将三个实例化对象添加到线性表中。最后使用Collections.sort()方法排序。使用sort方法需要保证对象是可比较的,要么使用Comparable接口重写compareTo方法,要么使用Comparator接口重写compare方法。上面代码在类外重写compare方法。

  • 一些排序和比较的编程设计

(1)编写一条语句,按最后一个字符的递增顺序,对名为list的字符串的ArrayList进行排序

list.sort((e1, e2) -> {
  if (e1.length() == 0) 
    return -1;
  else (e2.length() == 0)
    return 1;
  else
    return charAt(e1.size() - 1) - charAt(e2.size() - 1);
} 

(2)编写一条语句,按照double[][]的第二列的递增顺序对其二维数组进行排序。例如,如果数组double[][] x = {{3,1},{2,-1},{2,0}},则排序后的数组将是{{2,-1},{2,0},{3,1}

java.util.Arrays.sort(x, (e1, e2) -> (int)(e1[1] - e2[1]));

sort方法用于排序。参数 (e1, e2) -> (int)(e1[1] - e2[1]) 表达的是当相减返回负数时,交换。

完整代码:

public class Tst {
    public static void main(String args[]){
        double[][] x = {{3, 1}, {2, -1}, {2, 0}};

        java.util.Arrays.sort(x, (e1, e2) -> (int)(e1[1] - e2[1]));

        for(int i = 0; i < 3; i++){
            for(int j = 0; j < 2; j++){
                System.out.print(x[i][j] + " ");
            }
            System.out.println();
        }

    }
}

五、Collections类:执行集合和线性表中通用操作的静态方法

这个类里有很多静态方法:升序排序sort、降序排列Collections.sort(list, Collection.reverseOrder( ))、逆序排列reverse、查找键值binarySearch、随机重新排序shuffle、复制copy等等。


六、Vector类和Stcak类:向量类和栈类

Vector类是AbstractList类的子类(注意,ArrayList类也是AbstractList类的子类):

 而栈类Stack类是Vector类的子类:


七、队列

队列和栈是一种特殊的,对插入、删除加以限制的线性表。

1、Queue接口:队列

Queue接口中的方法实现对队列的操作:

 2、Deque接口:双端队列

链表类LinkedList类实现了Deque接口,而Deque接口又继承自Queue接口,所以我们可以使用LinkedList创建一个队列。

与Queue接口相比,Deque接口增加了从队列两端插入和删除的方法:addFirst (E)、removeFirst()、getFirst();addLast (E)、removeLast()、getLast()。

3、PriorityQueue类:优先队列

PriorityQueue类实现一个优先队列。优先队列中元素被赋予优先级。当访问元素时,拥有最高优先级的元素首先被删除。 

 比如:创造一个将元素的自然顺序颠倒的优先队列 ---

new PriorityQueue(初始容量, Collections.reverseOrder())

八、栈类实操:表达式求值

import java.util.Stack;

public class EvaluateExpression {
    public static void main(String[] args){
        if(args.length != 1){
            System.out.println("Usage: java EvaluateExpression \"expression \"");
            System.exit(1);
        }
        try{
            System.out.println(evaluateExpression(args[0]));
        }
        catch(Exception e){
            System.out.println("Wrong expression: " + args[0]);
        }
    }

    public static int evaluateExpression(String expression){
        Stack<Integer> operandStack = new Stack<>();    // new Stack1
        Stack<Character> operatorStack = new Stack<>();    // new Stack2

        //提取被空格分隔的操作符、操作数和括号
        expression = insertBlanks(expression);    //保证操作符、操作数、括号至少被一个空格分隔
        String[] tokens = expression.split("");

        for(String token: tokens){
            if(token.length() == 0)
                continue;   //标记为空,跳过
            else if(token.charAt(0) == '+' || token.charAt(0) == '-'){  // + or -
                while(!operandStack.isEmpty() &&
                        (operatorStack.peek() == '+' ||
                         operatorStack.peek() == '-' ||
                         operatorStack.peek() == '*' ||
                         operatorStack.peek() == '/')){
                    processAnOperator(operandStack,operatorStack);
                }
                operatorStack.push(token.charAt(0));    //压入operatorStack
            }
            else if(token.charAt(0) == '*' || token.charAt(0) == '/'){  // * or /
                while(!operatorStack.isEmpty() &&
                      (operatorStack.peek() == '*' ||
                       operatorStack.peek() == '/')){
                    processAnOperator(operandStack,operatorStack);
                }
                operatorStack.push(token.charAt(0));    //压入operatorStack
            }
            else if(token.trim().charAt(0) == '('){     // (
                operatorStack.push('(');    压入operatorStack
            }else if(token.trim().charAt(0) == ')'){    // )
                while(operatorStack.peek() != '('){     //处理栈顶的所有操作符直到看到)符号为止
                    processAnOperator(operandStack, operatorStack);
                }
                operatorStack.pop(); // 然后从栈中弹出)
            }
            else{
                operandStack.push(new Integer(token));  //标记是操作数,压入operandStack
            }
        }
        while(!operatorStack.isEmpty()){    //处理剩余的操作符
            processAnOperator(operandStack, operatorStack);
        }
        return operandStack.pop();
    }

    //处理一个操作符
    //从operatorStack中弹出一个操作符,从operandStack弹出两个操作数。然后完成对应的操作后,将结果压回operandStack中
    public static void processAnOperator(Stack<Integer> operandStack,Stack<Character> operatorStack){
        char op = operatorStack.pop();
        int op1 = operandStack.pop();
        int op2 = operandStack.pop();
        if(op == '+')
            operandStack.push(op2 + op1);
        else if(op == '-')
            operandStack.push(op2 - op1);
        else if(op == '*')
            operandStack.push(op2 * op1);
        else if(op == '/')
            operandStack.push(op2 / op1);
    }

    //保证操作符、操作数、括号至少被一个空格分隔
    public static String insertBlanks(String s){
        String result = "";

        for(int i = 0; i < s.length(); i++){
            if(s.charAt(i) == '(' || s.charAt(i) == ')'||
               s.charAt(i) == '+' || s.charAt(i) == '-'||
               s.charAt(i) == '*' || s.charAt(i) == '/')
                result += " " + s.charAt(i) + " ";
            else
                result += s.charAt(i);
        }
        return result;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

颜 然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值