目录
-
第一章 线性与非线性结构
数据结构包括线性结构与非线性结构
线性与非线性结构的常见代表:
线性结构:链表,队列,栈,一维数组
非线性结构:图,树,多维数组,广义表
1.1线性结构![](https://i-blog.csdnimg.cn/blog_migrate/4891b57bef2fd8ffb6381f70169b24c5.png)
线性结构是数据结构中最常见的数据类型,其特点是元素之间有着一对一的关系,即除首尾节点之间,每一个节点都有唯一的前驱与后继
线性结构有两种不同的存储结构,即顺序存储结构(数组)和链式存储结构(链表)
顺序存储结构:以顺序存储结构存储的线性表被称为顺序表,特点是存储空间连续,并且便于查改(因为可以通过下标来查找,时间复杂度为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、数组占连续的内存所以不需要考虑到下一个指向问 题,而链表可以不占连续的内存,所以要考虑下一个的指向问题,这也是侧重点;
- 两个侧重不同,数组取出其中的数据比较方便,只要直接去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;
}
}
}