java——数据结构笔记(更新至链表实现)

目录

第一章 线性与非线性结构

1.1线性结构​编辑

1.2 非线性结构

第二章 稀疏数组

2.1需求分析

2.2基本介绍

2.3 二维数组转稀疏数组

2.3.1 思路

2.3.2 代码

2.4 稀疏数组转二维数组

2.3.1 思路

2.3.2 代码

第三章 队列

3.1 基本介绍

3.2 普通队列实现

3.2.1 数组实现队列

3.2.1 数组实现队列

3.2 环形队列实现

3.2.1 环形队列的数组实现

3.2.2 环形队列的链表实现(可略)

第四章 链表

4.1基本介绍

4.2单链表代码实现

4.2.1创建节点类

4.2.2链表功能代码


  • 第一章 线性与非线性结构

数据结构包括线性结构与非线性结构

线性与非线性结构的常见代表:

线性结构:链表,队列,栈,一维数组

非线性结构:图,树,多维数组,广义表

1.1线性结构

线性结构是数据结构中最常见的数据类型,其特点是元素之间有着一对一的关系,即除首尾节点之间,每一个节点都有唯一的前驱与后继

线性结构有两种不同的存储结构,即顺序存储结构(数组)和链式存储结构(链表)

顺序存储结构:以顺序存储结构存储的线性表被称为顺序表,特点是存储空间连续,并且便于查改(因为可以通过下标来查找,时间复杂度为o(1))

链式存储结构:以链式存储结构存储的线性表被称作链表,特点是存储空间不连续,并且便于增删(因为只需要将后面的指针指向改变,时间复杂度为o(1))

1.2 非线性结构

  • 第二章 稀疏数组

2.1需求分析

当一个二维数组中有许多重复的无意义的值时,为了减少存储空间的占用,就可以采用稀疏数组的方式来存储数据

2.2基本介绍

定义稀疏数组时,第一行代表这个要存储的二维数组的规模和有效的值有多少个,剩下的数据就是根据有效值的个数循环输入数据,行、列各代表该数据在二维数组中的第几行第几

2.3 二维数组转稀疏数组

2.3.1 思路

1)遍历原始二维数组,读取有效数据数量sum.

2)创建稀疏数组sparseArray[sum+1][3]

3)定义row实现sparseArray数组的换行,循环将arr的数据输入sparseArray[row]中。

4)将得到的稀疏数组存盘(这一步没有展示)

2.3.2 代码

public static int[][] castSparseArray(int[][] arr) {
        // 原数组的总行数
        int row = arr.length;
        // 原数组的总列数
        int cloumn = arr[0].length;
        // 获取黑白子总数
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
        for (int j = 0; j < arr.length; j++) {
        if (arr[i][j] != 0) {
        sum++;
        }
        }
        }
        // 创建稀疏数组(sum+1 行表示 sum 个黑白子 + 1行规模)
        int[][] sparseArray = new int[sum + 1][3];
        // 第 1 行原二维数组的行、列、棋子总数
        sparseArray[0][0] = row;
        sparseArray[0][1] = cloumn;
        sparseArray[0][2] = sum;
        // 第 2 行开始存具体数据(每行都是一个棋子数据)
        int sparseRow = 0;
        for (int i = 0; i < arr.length; i++) {
        for (int j = 0; j < arr.length; j++) {
        if (arr[i][j] != 0) {
        sparseRow++;
        sparseArray[sparseRow][0] = i;
        sparseArray[sparseRow][1] = j;
        sparseArray[sparseRow][2] = arr[i][j];
        }
        }
        }
        return sparseArray;
        }

2.4 稀疏数组转二维数组

2.3.1 思路

1)从磁盘中读取稀疏数组(这里没有展示)

2)读取稀疏数组的第一行,并且根据数据初始化数组

3)遍历sparseArr(需要注意的是遍历时要从一开始),将sparseArr的值根据sparseArr的信息填入arr的正确位置

2.3.2 代码

public static int[][] castToArray(int[][] sparseArr) {
        // 获取稀疏数组第 1 行(记录了原数组的总行数和总列数)
        int[] rowFirst = sparseArr[0];
        // 初始化数组(棋盘)
        int row = rowFirst[0];
        int column = rowFirst[1];
        int[][] arr = new int[row][column];
        // 初始化数据(黑白子)
        for (int i = 1; i < sparseArr.length; i++) {
        int[] sparseRow = sparseArr[i];
        arr[sparseRow[0]][sparseRow[1]] = sparseRow[2];
        }
        return arr;
        }

第三章 队列

3.1 基本介绍

1)队列是一个有序列表,可以用数组或是链表来实现

2)遵循先入先出的原则。即:先存入的数据要先取出,后存入的数据要后取出

3.2 普通队列实现

队列的实现有两种,一种是用数组实现,另一种使用链表实现,

同样,链表与数组的异同也会在队列中体现出来。

异:1、数组占连续的内存所以不需要考虑到下一个指向问                       题,而链表可以不占连续的内存,所以要考虑下一个的指向问题,这也是侧重点;

  1. 两个侧重不同,数组取出其中的数据比较方便,只要直接去array[i]即可,而链表就必须遍历了,这个比较麻烦; 数组增删比较麻烦,要把其中每个数据都取出重新放入新数组,而链表就比较方便只需改变前后两个节点的指向即可。

同:1、都是一种数据结构,用来存储数据并实现先进先出的操作。

2、都支持入队和出队操作。

3.2.1 数组实现队列

思路分析:实现队列所需要的数据有 maxsize:定义队列的最大值front:定义队列的头指针,会根据队列的输出而变动位置rear:定义队列的尾指针,会根据队列的输入而变动位置,同时它也是判断队列是否未满的关键依据(rear+1==maxsize)

代码:

public class Main {
    public static void main(String[] args) {
        Arrayqueue queue=new Arrayqueue(4);
        queue.addQueue(1);
        queue.addQueue(2);
        queue.addQueue(3);
        queue.addQueue(4);
        queue.showQueue();
        System.out.println("出队元素为:"+queue.getQueue());
        System.out.println("出队元素为:"+queue.getQueue());
        System.out.println("出队元素为:"+queue.getQueue());
    }
}
class Arrayqueue {
    private int maxSize;
    private int[] queueArray;
    private int front;
    private int rear;
    public Arrayqueue(int maxSize) {
        this.maxSize = maxSize;
        queueArray = new int[maxSize];
//        指向队列头部,分析出front是指向队列头的前一个位置
        front = -1;
//        指向队列尾部
        rear = -1;
    }
    public boolean isFull() {
        return rear == maxSize - 1;
    }
    public boolean isEmpty() {
        return rear == front;
    }
    public void addQueue(int j) {
        if (isFull()) {
            System.out.println("队列已满,不能加入数据");
            return;
        }
        rear++;
        queueArray[rear] = j;
    }
    public int getQueue() {
        if (isEmpty()) {
//            通过抛出异常来提示队列为空,不能读数据
            throw new RuntimeException("队列为空,不能取数据");
        }
        front++;
        return queueArray[front];
    }
    public void showQueue() {
        if (isEmpty()) {
            System.out.println("队列为空");
            return;
        }
        for (int i = 0; i < queueArray.length; i++) {
            System.out.printf("queueArray[%d]=%d\n", i, queueArray[i]);
        }
    }
    public int showQueueHead() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        return queueArray[front + 1];
    }
}

3.2.1 数组实现队列

思路分析:实现队列所需要的数据有 head:定义队列的头节点tail:定义队列的尾结点,会根据队列的输出而变动位置

代码:

public class LinkedQueue {
    private Node head;
    private Node tail;
    // 添加元素到队列
    public void enqueue(int data) {
        Node newNode = new Node(data);
        if (head == null && tail == null) {
            head = newNode;
            tail = newNode;
        } else {
            tail.next = newNode;
            tail = newNode;
    }
    }
   // 移除队列的第一个元素
    public int dequeue() {
        if (head == null && tail == null) {
            System.out.println("队列为空");
            return -1;
        }
        int data = head.data;
        head = head.next;
        return data;
    }
    // 查看队列的第一个元素
    public int peek() {
        if (head == null && tail == null) {
            System.out.println("队列为空");
            return -1;
        }
        return head.data;
    }
    // 检查队列是否为空
    public boolean isEmpty() {
        return head == null && tail == null;
    }
}
class Node {
    int data;
    Node next;
    public Node(int data) {
        this.data = data;
        this.next = null;
    }
}

3.2 环形队列实现

3.2.1 环形队列的数组实现

因为普通的队列在使用后他们的front或head无法回到原来的位置,所以他们的数组或者链表只能使用一次,对空间的复用率极低,因此就有了,另一种队列的存在方式——环形队列

思路分析:

1、front的定义含义进行调整:front指向队列的第一个元素,也就是arr[front]就是队列的第一个元素,front初值设置为0

2、rear的定义含义进行调整:rear初始值等于front。rear指向队列最后一个元素的后一个位置

3、当队列满时,条件变为(rear+1)%maxsize=front

4、当队列空时,rear==front

代码:

public class CircleQueue {
//    表示最大值
    private int maxSize;
//    用于存放数据的数组
    private int[] arr;
    private int front;
    private int rear;
    public CircleQueue(int maxSize) {
        this.maxSize = maxSize;
        arr = new int[maxSize];
//        与普通队列不同,普通队列的front的初值为-1
        front = 0;
        rear = 0;
    }

    public boolean isFull() {
//        与普通队列不同,普通队列的判满条件是rear+1==maxSize
        return (rear+1)%maxSize == front;
    }
    public  boolean isEmpty() {
//        与普通队列相同,
        return rear == front;
    }
    public void enqueue(int value) {
        if(isFull())
        {
            System.out.println("队列已满,不能加入数据");
            return;
        }
        arr[rear] = value;
//        rear后移,考虑到循环队列,因此要对他取模
        rear=(rear+1)%maxSize;
    }
    public int dequeue() {
        if(isEmpty())
        {
//            抛出异常
            throw new RuntimeException("队列为空,不能进行出队操作");
        }
        int value = arr[front];
        front=(front+1)%maxSize;
        return value;
    }
    public void showQueue() {
        if(isEmpty())
        {
            System.out.println("队列为空");
            return;
        }
        for (int i = front; i < front+size(); i++) {
            System.out.println("arr["+i%maxSize+"]="+arr[i % maxSize]);
        }
    }
//    这是求循环队列的有效长度,并非最大长度。
    private int size() {
//        与普通队列不同,普通队列的size()返回rear-front,
        return (rear+maxSize-front)%maxSize;
    }
    public  int headQueue() {
        if(isEmpty())
        {
            throw new RuntimeException("队列为空");
        }
        return arr[front];
    }

    public static void main(String[] args) {
        CircleQueue circleQueue = new CircleQueue(4);
        circleQueue.enqueue(1);
        circleQueue.enqueue(2);
        circleQueue.enqueue(3);
        circleQueue.enqueue(4);
        circleQueue.enqueue(5);
        circleQueue.showQueue();
        System.out.println("队列长度"+circleQueue.size());
        System.out.println("队头元素"+circleQueue.headQueue());
        int num=circleQueue.dequeue();
        System.out.print("弹出一个"+num+"元素后:");
        circleQueue.showQueue();
    }
}

3.2.2 环形队列的链表实现(可略)

因为链队列本身就不限大小,而且根据jvm的垃圾回收机制会自动将用完的节点回收,因此一般不会有人用链表写环形队列,硬要写的话其实就时普通的链队列写法

代码:

public class CircularQueue {
    private Node head;
    private Node tail;
    private int size;
    private int capacity;
    public CircularQueue(int capacity) {
        this.capacity = capacity;
        this.size = 0;
    }
    public void enqueue(int data) {
        if (size == capacity) {
            System.out.println("Queue is full");
            return;
        }
        Node newNode = new Node(data);
        if (head == null) {
            head = newNode;
            tail = newNode;
            tail.next = head;
        } else {
            tail.next = newNode;
            tail = newNode;
            tail.next = head;
        }
        size++;
    }
    public int dequeue() {
        if (size == 0) {
            System.out.println("Queue is empty");
            return -1;
        }
        int data = head.data;
        head = head.next;
        size--;
        return data;
    }
    private static class Node {
        int data;
        Node next;
        public Node(int data) {
            this.data = data;
            this.next = null;
        }
    }
    public static void main(String[] args) {
        CircularQueue queue = new CircularQueue(5);
        queue.enqueue(1);
        queue.enqueue(2);
        queue.enqueue(3);
        queue.enqueue(4);
        queue.enqueue(5);
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        queue.enqueue(6);
        queue.enqueue(7);
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
    }
}

 链表

4.1基本介绍

①链表是以节点储存的,因此在写代码时,首先要完成链表的节点类,其次你的链表想要储存的数据就要在节点类中完成,因此你的节点类要包含数值域和指针域

②链表的存储地址是不连续的,因此链表的内存空间利用率相对较低。

③因为链表的节点之间是有指针的存在,而有指针就代表该数据结构的增删操作会相对快

④链表分为头节点存数据和头节点不存数据的类型,一般头节点都不存放数据,因为避免了对空链表的特殊处理。这样可以简化代码逻辑,减少了实现复杂度。

链表分为单链表,双向链表,循环链表等,这里先介绍单链表

4.2单链表代码实现

4.2.1创建节点类

class Linknode{
//    首先要定义节点类
//    要在节点类中定义节点的内容和指向下一个节点的指针
    int data;
    Linknode next;
//    构造器
    Linknode(int data) {
        this.data=data;
        this.next=null;
    }
//    为了显示方便,我们重写toString方法
    @Override
    public String toString() {
        return "Linknode{" +
                "data=" + data +
                ", next=" + next +
                '}';
    }
}

4.2.2链表功能代码

PS:链表的功能代码十分详细,因此测试类也很长,就不在这里写了,想要的可以在博主的csdn,b站上联系,我会私给你

import java.util.HashSet;
import java.util.LinkedList;

class Linknode{
//    首先要定义节点类
//    要在节点类中定义节点的内容和指向下一个节点的指针
    int data;
    Linknode next;
//    构造器
    Linknode(int data) {
        this.data=data;
        this.next=null;
    }
//    为了显示方便,我们可以重写toString方法
    @Override
    public String toString() {
        return "data=" + data ;
    }
}

public class LinkList {
//    初始化头节点,一般不要动他所以用private,而head不放具体数据的原因主要是因为
//    链表为空时,不让链表为null,所以用一个头节点来代替null,防止了空指针问题
    private Linknode head=new Linknode(0);

//    尾插法插入数据
    public void AddtoLast(int val) {
//        定义一个新节点,储存数据
        Linknode New=new Linknode(val);
//        定义一个现节点,遍历数据
        Linknode Cur=head;
        while(Cur.next!=null) {
            Cur=Cur.next;
        }
        Cur.next=New;
    }


//    在指定位置插入数据
    public void Addindex(int index,int val)
    {
        Linknode New=new Linknode(val);
        Linknode Cur=head;
        for(int i=0;i<index;i++)
        {
            Cur=Cur.next;
        }
        New.next=Cur.next;
        Cur.next=New;
    }


//        删除指定位置的数据
    public void Deleteindex(int index) {
        Linknode Cur = head;
        for (int i = 0; i < index; i++) {
            Cur = Cur.next;
        }
        Cur.next = Cur.next.next;
    }


//            修改指定位置的数据
    public void update(int index,int val)
    {
        Linknode Cur = head;
        for (int i = 0; i < index; i++) {
            Cur = Cur.next;
        }
        Cur.next.data=val;
    }




//      查找指定元素的首次出现的位置
    public int  search(int val){
        Linknode Cur = head;
        int index=0;
        while(Cur.next!=null)
        {
            Cur=Cur.next;
            if(Cur.data==val)
            {
                return index;
            }
            index++;
        }
//                如果没有找到,抛出异常,因此在使用search时要对他进行try catch环绕
        throw new RuntimeException("没有找到");
    }

//  链表的交集
    public LinkList intersection(LinkList list2) {
        LinkList result = new LinkList();
        HashSet<Integer> set = new HashSet<>();

        Linknode current = head.next;
        while (current != null) {
            set.add(current.data);
            current = current.next;
        }

        current = list2.head.next;
        while (current != null) {
            if (set.contains(current.data)) {
                result.AddtoLast(current.data);
            }
            current = current.next;
        }

        return result;
    }
//链表的并集
    public LinkList union(LinkList list2) {
        LinkList result = new LinkList();
        HashSet<Integer> set = new HashSet<>();

        Linknode current = head.next;
        while (current != null) {
            set.add(current.data);
            result.AddtoLast(current.data);
            current = current.next;
        }

        current = list2.head.next;
        while (current != null) {
            if (!set.contains(current.data)) {
                result.AddtoLast(current.data);
            }
            current = current.next;
        }

        return result;
    }

//链表1对于链表2的差集
    public LinkList complement(LinkList list2) {
        LinkList result = new LinkList();
        HashSet<Integer> set = new HashSet<>();

        Linknode current = list2.head;
        while (current != null) {
            set.add(current.data);
            current = current.next;
        }

        current = head;
        while (current != null) {
            if (!set.contains(current.data)) {
                result.AddtoLast(current.data);
            }
            current = current.next;
        }
        return result;
    }
//        遍历链表
    public void show() {
        if(head.next==null)
        {
            System.out.println("链表为空");
        }
        Linknode Cur = head.next;
        while (Cur != null) {
            System.out.println(Cur);
            Cur = Cur.next;
        }
    }
}

  • 61
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值