数据结构——线性表

线性表是一种基本的数据结构,它可以用来存储一串有序的元素。线性表的基本实现通常有两种方式:数组和链表。

  1. 数组实现线性表:使用数组作为数据的存储结构,数组中每个位置存储一个元素,通过下标来访问元素。数组实现线性表的优点是访问元素速度快,缺点是插入和删除元素时需要移动其他位置的元素,效率较低。

  2. 链表实现线性表:使用节点和指针来存储数据。节点包含一个数据元素和一个指向下一个节点的指针,最后一个节点的指针为空。链表实现线性表的优点是插入和删除元素时不需要移动其他位置的元素,效率较高,缺点是访问元素时需要从头节点开始遍历直至找到目标元素,效率较低。

综合来看,数组实现线性表适合静态数据或者访问元素频繁的场景,而链表实现线性表适合动态数据或者插入、删除元素频繁的场景。

顺序表

顺序表(Sequential List)是一种线性表的存储结构,它利用一段连续的存储空间来存储线性表中的元素,并且通过数组下标来访问元素。

顺序表包含两部分:数据区和长度标记。数据区用来存储线性表中的元素,长度标记用来表示当前顺序表中有效元素的个数。

在顺序表中,元素在物理地址上是连续存储的,因此可以通过下标直接随机访问元素。顺序表的优点是访问元素方便快捷,缺点是插入和删除操作需要移动其他元素,时间复杂度较高。

常见的顺序表操作包括插入元素、删除元素、查找元素、修改元素以及获取顺序表长度等。顺序表还可以通过扩容和缩容来动态调整存储空间的大小。

在实现顺序表时,通常会使用数组来存储元素,并且使用一个变量来记录当前顺序表中有效元素的个数。通过对数组的操作,可以实现顺序表的各种功能。

package com.zlk.line;


import java.util.Iterator;

/**
 * @author zlk
 * @date 2023年08月23日 9:48
 * 线性表API方法实现
 */
public class SequenceList implements Iterable {
    private T[] eles;
    private int N;
    //构造方法
    public SequenceList(int capacity) {
        this.eles = (T[])new Object[capacity];
        this.N =0 ;
    }
    //将一个线性表置为空表
    public void clear(){
        this.N=0;
    }
    //

    //判断当前线性表是否为空表
    public boolean isEmpty(){
        return N==0;
    }
    //获取线性表的长度
    public int length(){
        return N;
    }
    //获取指定位置的元素
    public T get(int i){
        return eles[i];
    }
    //向线型表中添加元素t
    public void insert(T t){
        if (N==eles.length){
            resize(2*eles.length);
        }
        eles[N++]=t;
    }
    //指定地方插入元素
    public void inByindex(int i,T t){
        if (N==eles.length){
            resize(2*eles.length);
        }
        for (int j = N; j >i; j--) {
            eles[j]=eles[j-1];
        }
        eles[i]=t;
        N++;
    }
    //删除指定地方的元素
    public T remove(int index){
        T t=eles[index];
        for (int i = index; i  iterator() {
        return new SIterator();
    }
    private class SIterator  implements Iterator{
        private int cusor;
        public SIterator() {
            this.cusor = 0;
        }
        @Override
        public boolean hasNext() {
            return cusor

单向链表

单向链表是一种常见的数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。

每个节点中存储的数据元素可以是任意类型,根据实际需求而定。指针指向下一个节点的地址,用于将链表中的节点连接起来。链表的最后一个节点指针通常指向空。

相比于数组,链表的优点是可以动态地添加和删除元素,不需要预先分配内存空间。然而,链表的缺点是访问元素时需要遍历整个链表,效率较低。

常见的链表操作包括:
1. 插入:将新元素插入到链表的指定位置,需要改变相应节点的指针指向。
2. 删除:删除链表中的某个元素,需要将相应节点的前一个节点指针指向下一个节点,并释放删除节点的内存空间。
3. 访问:从链表中获取某个位置的元素,需要遍历整个链表直到找到目标位置。
4. 查找:根据元素的值查找其在链表中的位置。

需要注意的是,链表的头节点即第一个节点用于表示整个链表,因此链表的操作通常从头节点开始。

单向链表的一个典型示例是单链表(Singly Linked List),它只有一个指向下一个节点的指针。另外还有双向链表(Doubly Linked List),每个节点除了有指向下一个节点的指针,还有指向前一个节点的指针。

package com.zlk.line;

import java.util.Iterator;

/**
 * @author zlk
 * @date 2023年08月26日 12:24
 * 单向链表的实现
 */
public class LinkList  implements Iterable{
    //记录头节点
    private Node head;
    //记录链表的长度
    private int N;


    private class Node{
        T item;
        Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
    public LinkList() {
        this.head = new Node(null,null);
        N = 0;
    }
    //清空链表
    public void clear(){
        head.next=null;
        this.N=0;
    }
    //获取链表的长度
    public int length(){
        return N;
    }
    //判断链表是否为空
    public boolean isEmpty(){
        return N==0;
    }
    //获取指定位置出的元素
    public T get(int i){
        //通过循环,从头结点往后找,以此找i次
        Node n = head.next;
        for (int j = 0; j < i; j++) {
            n=n.next;
        }
        return n.item;
    }
    //添加元素
    public void insert(T t){
        //找到最后一个节点
        Node n=head;
        while (n.next!=null) {
            n = n.next;
        }
        //创建新结点,保存元素t
        Node newnode = new Node(t, null);
        //让当前最后一个结点指向新结点
        n.next=newnode;
        N++;
    }
    //在指定位置处添加添加元素
    public void insert(int i,T t){
        Node n=head;
        Node newnode = new Node(t, null);
        for (int j = 0; j <= i-1; j++) {
            n=n.next;
        }

        Node n1 = n.next;
        n.next=newnode;
        newnode.next=n1;
        N++;
    }
    //删除指定位置出的节点
    public T remove(int i){
        Node pre = head;
        for(int index=0;index<=i-1;i++){
            pre=pre.next;
        }
        //要找到位置的结点
        Node curr = pre.next;
        Node nextNode = curr .next ;
        pre.next=nextNode;
        N--;
        return curr.item;
    }
    //查找元素第一次出现的位置
    public int indexOf(T t) {
        Node n = head;
        for (int i = 0; n.next != null; i++) {
            n = n.next;
            if (n.item.equals(t)) {
                return i;
            }
        }
        return -1;
    }
    @Override
    public Iterator iterator() {
        return new LIterator();
    }
    private class LIterator implements Iterator {
        private Node n;
        public LIterator() {
            this.n = head;
        }

        @Override
        public boolean hasNext() {
            return n.next!=null;
        }

        @Override
        public Object next() {
           n=n.next;
           return n.item;
        }

        @Override
        public void remove() {

        }
    }
    //用来反转整个链表
    public void reverse(){
        if (isEmpty()){
            return;
        }
        reverse(head.next);
    }
    //反转指定的结点curr,并把反转后的结点返回
    public Node reverse(Node curr){
        if (curr.next==null){
            head.next=curr;
            return curr;
        }
        Node pre = reverse(curr.next);
        //让返回的结点的下一个结点变成当前结点curr
        pre.next=curr;
        //把当前结点的下一个结点变成null
        curr.next=null;
        return curr;
    }
}

判断链表是否有环,有则返回环的入口

package com.zlk.line;

/**
 * @author zlk
 * @date 2023年08月27日 0:05
 * 快慢指针判断是否有环问题
 */
public class FastslowTest {
    private static class Node{
        public T item;
        public Node next;
        public Node(T item,  Node next) {
            this.item=item;
            this.next=next;
        }
    }
    public static void main(String[] args) {
        Node first = new Node(  "aa", null);

        Node second = new Node(  "bb", null);

        Node third = new Node(  "cc", null);
        Node fourth = new Node(  "dd", null);
        Node fifth = new Node(  "ee", null);
        Node six = new Node(  "ff", null);
        Node seven = new Node(  "gg", null);
        //完成结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //产生环
        seven.next = third;
        Node entrance = getEntrance(first);
        System.out.println(entrance.item);


    }
    public static Node getEntrance(Node first){
        Node fast=first;
        Node slow=fast;
        Node temp=null;

        while (fast!=null && fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if (fast.equals(slow)){
                temp=first;
                continue;
            }
            if (temp!=null && temp.next!=null){
               temp=temp.next;
               if (temp.equals(slow)){
                   return temp;
               }
            }
        }
        return null;
    }
    public static Node getEntrance2(Node first) {
        Node slow = first;
        Node fast = first;
        Node temp = null;
        while(fast!=null && fast.next!=null){
            fast = fast.next.next;
            slow=slow.next;
            if (fast.equals(slow)){
                temp = first;
                continue;
            }
            if (temp!=null){
                temp=temp.next;
                if (temp.equals(slow)){
                    return temp;
                }
            }
        }
        return null;
    }






}

约瑟夫环问题

package com.zlk.line;

/**
 * @author zlk
 * @date 2023年08月28日 19:47
 * 约瑟夫环问题
 */
public class JosephLinkTest {
    private static class Node{
        public T item;
        public Node next;
        public Node(T item,  Node next) {
            this.item=item;
            this.next=next;
        }
    }
    public static void main(String[] args) {
        //1.构建循环链表
        Node first = null;
//记录前一个结点
        Node pre = null;
        for (int i = 1; i <= 41; i++) {
//第一个元素
            if (i==1){
                first = new Node(i,null);
                pre = first;
                continue;
            }
            Node node = new Node<>(i,null);
            pre.next = node;
            pre = node;
            if (i==41){
//构建循环链表,让最后一个结点指向第一个结点
                pre.next=first;
            }
        }
//2.使用count,记录当前的报数值
        int count=0;
//3.遍历链表,每循环一次,count++
        Node n = first;
        Node before = null;
        while(n!=n.next){
//4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
            count++;
            if (count==3){
//删除当前结点
                before.next = n.next;
                System.out.print(n.item+",");
                count=0;
                n = n.next;
            }else{
                before=n;
                n = n.next;
            }
        }
        /*打印剩余的最后那个人*/
        System.out.println(n.item);
    }
}

双向链表

双向链表是一种常见的数据结构,它由一系列的节点组成,每个节点包含一个数据元素和两个指针,一个指向前一个节点,一个指向后一个节点。

与单向链表不同,双向链表可以在任意节点上进行双向遍历。这使得在某些情况下,双向链表的操作更加方便和高效。

双向链表的节点结构一般包含三个部分:数据域、前驱指针和后继指针。通过前驱指针和后继指针,每个节点都能够与相邻的节点进行连接。

双向链表的基本操作包括:
1. 在链表的头部插入一个新节点。
2. 在链表的尾部插入一个新节点。
3. 在指定位置插入一个新节点。
4. 删除指定位置的节点。
5. 获取链表的长度。
6. 在链表中搜索特定值。

相比于单向链表,双向链表具有一些优点和缺点:
优点:
1. 可以在任意位置进行双向遍历。
2. 插入和删除节点的操作更加高效,不需要像单向链表那样找到前一个节点。

缺点:
1. 双向链表相对于单向链表需要更多的指针空间,占用更多的内存。
2. 在插入或删除节点时,需要维护指针的正确性,增加了编程的复杂性。

总的来说,双向链表是一种灵活、高效的数据结构,可以在任意位置进行双向遍历和更高效地进行插入和删除操作。但是由于额外的指针需求和维护的成本,需要根据具体需求进行权衡。

package com.zlk.line;

import java.util.Iterator;

/**
 * @author zlk
 * @date 2023年08月26日 15:51
 * 双向链表的实现
 */
public class TowWatLinkList implements Iterable{
    private Node head;
    private Node last;
    private int N;
    //存储数据

    private class Node{
        public T item;
        public Node pre;
        public Node next;
        public Node(T item,Node pre,Node next) {
            this.item=item;
            this.pre=pre;
            this.next=next;
        }
    }

    public TowWatLinkList() {
       //初始化头,尾节点
        this.head=new Node(null,null,null);
        this.last=null;
        //初始化个数
        this.N=0;
    }
    //清空链表
    public void clear(){
        this.head.next=null;
        this.last=null;
        this.N=0;
    }
    //返回链表的长度
    public int length(){
        return this.N;
    }
    //判断是否为空
    public boolean isEmpty(){
        return this.N==0;
    }
    //获取第一个元素
    public T getFirst(){
        if (isEmpty()){
            return null;
        }
        return head.next.item;
    }
    //获取最后个元素
    public T getLast(){
        if (isEmpty()){
            return null;
        }
        return last.item;
    }
    //添加元素
    public void insert(T t){
        if (isEmpty()){
            Node newnode = new Node(t, head, null);
            last=newnode;
            head.next=newnode;
        }else {
            Node oldlast=last;
            //创建
            Node newnode = new Node(t, last, null);
            oldlast.next=newnode;
            last=newnode;
        }
        N++;
    }
    //添加元素
    public void insert(int i,T t){
        //找到i位置前一个元素
        Node pre=head;
        for (int j = 0; j  implements Iterator{
        private Node n;

        public TIterator() {
          this.n= head;
        }

        @Override
        public boolean hasNext() {
            return n.next!=null;
        }

        @Override
        public Object next() {
            n=n.next;
            return n.item;
        }

        @Override
        public void remove() {

        }
    }
}

栈(Stack)是一种常见的数据结构,它遵循“后进先出”(Last-In-First-Out,LIFO)的原则。栈的基本操作包括压栈(push)和弹栈(pop)。

栈可以简单地用一个线性表实现,它有一个固定的顶部(top)和一些元素。新元素总是被添加到栈的顶部,而栈中的元素只能通过弹栈操作以相反的顺序移除。

栈的主要操作如下:
1. Push(压栈):将一个新元素添加到栈的顶部。
2. Pop(弹栈):从栈的顶部移除一个元素。
3. Top(栈顶):获取栈顶元素,不对栈进行修改。
4. IsEmpty(判空):检查栈是否为空。
5. Size(大小):获取栈中元素的个数。

栈可以用于许多应用场景,例如:
- 表达式求值:使用栈来处理括号匹配和运算符优先级。
- 函数调用:保存函数调用信息和变量,在函数完成后按相反的顺序进行恢复。
- 浏览器的后退和前进:使用栈来记录浏览历史。

栈的实现方式包括使用数组和链表。使用数组实现的栈称为顺序栈(Array Stack),使用链表实现的栈称为链式栈(Linked Stack)。

需要注意的是,在使用栈时要注意栈溢出(Stack Overflow)的问题,即压入过多元素导致栈的空间超出限制。

package com.zlk.line;

import java.util.Iterator;

/**
 * @author zlk
 * @date 2023年08月28日 22:37
 * 栈的实现
 */
public class Stack implements Iterable{
    private Node head;
    private int N;
    private static class Node{
        public T item;
        public Node next;
        public Node(T item,  Node next) {
            this.item=item;
            this.next=next;
        }

    }
    public Stack() {
        this.head = new Node(null,null);
        N = 0;
    }
    //获取栈中元素的个数
    public int size(){
        return N;
    }
    //判断当前栈中元素个数是否为0
    public boolean isEmpty(){
        return N==0;
    }

    //添加
    public void push(T t){
        Node oldfirst = head.next;
        Node newNode = new Node<>(t, null);
        head.next=newNode;
        newNode.next=oldfirst;
        N++;
    }
    //删除
    public T pop(){
        Node oldfirst = head.next;
        if (oldfirst==null){
            return null;
        }
        //删除首个元素
        head.next = head.next.next;
        //个数-1
        N--;
        return (T) oldfirst.item;
    }

    @Override
    public Iterator iterator() {
        return new SIterator();
    }
    public class SIterator implements Iterator{

        private Node n;
        public SIterator() {
            this.n = head;
        }

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public Object next() {
           n=n.next;
           return n.item;
        }

        @Override
        public void remove() {

        }
    }
}

栈解决逆波兰表达式问题

package com.zlk.line;

/**
 * @author zlk
 * @date 2023年08月29日 8:13
 * 栈解决简单问题-----逆波兰表达式
 */
public class AntipolishStack {
    public static void main(String[] args) {
        String str="3*(8-4)/2+9-(8+2)";
        String[] s={"3","8","4","-","*","2","/","9","+","8","2","+","-"};
        Stack stack = new Stack<>();
        for (int i = 0; i < s.length; i++) {
            if ("-".equals(s[i])){
                Integer n = stack.pop();
                Integer m = stack.pop();
                Integer v=m-n;
                stack.push(v);
            }else if("*".equals(s[i])){
                Integer n = stack.pop();
                Integer m = stack.pop();
                Integer v=m*n;
                stack.push(v);
            }else if("+".equals(s[i])){
                Integer n = stack.pop();
                Integer m = stack.pop();
                Integer v=m+n;
                stack.push(v);
            }else if("/".equals(s[i])){
                Integer n = stack.pop();
                Integer m = stack.pop();
                Integer v=m/n;
                stack.push(v);
            } else {
                stack.push(Integer.parseInt(s[i]));
            }
        }
        System.out.println(stack.pop());
        System.out.println(3*(8-4)/2+9-(8+2));

    }
}

栈判断左右括号是否成对

package com.zlk.line;

/**
 * @author zlk
 * @date 2023年08月29日 0:06
 * 栈接解决简单问题----左右括号
 */
public class BracketsMatchStack {
    public static void main(String[] args) {

        boolean match = isMatch("上(海((长安))(djb)()");
        System.out.println(match);
    }
    public static boolean isMatch(String str){
        Stack stack = new Stack<>();
        for (int i = 0; i < str.length(); i++) {
            String s= String.valueOf(str.charAt(i));
            if ("(".equals(s)){
                stack.push(1);
            }
            if (")".equals(s)){
                Object pop = stack.pop();
                if (pop==null){
                    return false;
                }
            }
        }
        if (stack.size()!=0){
            return false;
        }
       return true;
    }
}
 
  

队列

队列(Queue)是一种常见的数据结构,它遵循“先进先出”(First-In-First-Out,FIFO)的原则。队列的基本操作包括入队(enqueue)和出队(dequeue)。

队列可以简单地用线性表实现,它有一个固定的前部(front)和后部(rear),元素从后部添加到队列中,从前部移除。

队列的主要操作如下:
1. Enqueue(入队):将一个新元素添加到队列的后部。
2. Dequeue(出队):从队列的前部移除一个元素。
3. Front(队首):获取队列的前部元素,不对队列进行修改。
4. Rear(队尾):获取队列的后部元素,不对队列进行修改。
5. IsEmpty(判空):检查队列是否为空。
6. Size(大小):获取队列中元素的个数。

队列可以用于许多应用场景,例如:
- 线程调度:按照请求的顺序调度执行线程。
- 缓冲区管理:以FIFO的方式管理缓冲区中的数据。
- 广度优先搜索(BFS):使用队列对图进行广度优先搜索。

队列的实现方式包括使用数组和链表。使用数组实现的队列称为顺序队列(Array Queue),使用链表实现的队列称为链式队列(Linked Queue)。

需要注意的是,在使用队列时要注意队列为空(Empty Queue)和队列满(Full Queue)的问题,即无法继续出队或入队的情况。为了解决这些问题,可以使用循环队列(Circular Queue)或动态队列(Dynamic Queue)来实现。

package com.zlk.line;

import java.util.Iterator;

/**
 * @author zlk
 * @date 2023年09月03日 10:05
 * 队列的实现
 */
public class Queue implements Iterable{
    //记录首结点
    private Node head;
    //记录最后一个结点
    private Node last;
    //记录队列中元素的个数
    private int N;
    public Queue() {
        head = new Node(null,null);
        last=null;
        N=0;
    }

    private class Node{
        public T item;
        public Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
    //判断队列是否为空
    public boolean isEmpty(){
        return N==0;
    }
    //返回队列中元素的个数
    public int size(){
        return N;
    }
    //向队列中插入元素t
    public void enqueue(T t){
        if (last==null){
            last = new Node(t,null);
            head.next=last;
        }else{
            Node oldLast = last;
            last = new Node(t,null);
            oldLast.next=last;
        }
        //个数+1
        N++;
    }
    //从队列中拿出一个元素
    public T dequeue(){
        if (isEmpty()){
            return null;
        }
        Node oldFirst = head.next;
        head.next = oldFirst.next;
        N--;
        if (isEmpty()){
            last=null;
        }
        return oldFirst.item;
    }

    public Iterator iterator() {
        return new QIterator();
    }
    private class QIterator implements Iterator {
        private Node n = head;
        @Override
        public boolean hasNext() {
            return n.next!=null;
        }
        @Override
        public T next() {
            Node node = n.next;
            n = n.next;
            return node.item;
        }
        @Override
        public void remove() {

        }
    }



}

你可能感兴趣的:(链表,数据结构)