数据结构包括:线性结构和非线性结构
线性结构(一对一):
- 线性结构为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系。例:a[.]=1
- 线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。
- 链式存储的线性表成为链表,存储元素不一定是连续的,元素节点中存放数据元素及相邻元素的地址信息。
- 线性结构常见的有:数组、队列、链表和栈。
非线性结构(并非一对一的关系):
非线性 结构包括的有二维数组、多维数组、、广义表、树结构(*)、图结构(*)。
稀疏sparsearray数组和队列
当一个数组中大部分元素为0或者为同一个值的数组时,可以用稀疏数组来保存该数组。
稀疏数组的处理方法:
- 记录数组一共有几行几列,有多少个不同的值。
- 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模。
案例一:
[0]:表示一共几行几列,以及有几个值/
其他的表示第几行第几列的值为多少。
二维数组转 稀疏数组的思路
- 遍历原始的二位数组,得到有效数据的个数sum
- 根据sum就可以创建稀疏数组 sparsearray int[sum+1][3] int[行数][列]
- 将二维数组的有效数据存入稀疏数组
稀疏数组转原始的二维数组的思路
- 先读取稀疏数组的第一行,根据第一行的数据,创造原始的二维数组
- 在读取稀疏数组后几行的数据,并赋给原始的二维数组
sparsearray练习一:
public static void main(String[] args) {
//创建一个原始的二位数组 11*11
int chessArray[][] = new int[11][11];
chessArray[1][2] = 1;
chessArray[2][3] = 2;
for (int[] ints : chessArray) {
for (int data : ints) {
System.out.printf("%d\t",data);
}
System.out.println();
}
//将二维数组转为 稀疏数组
int sum = 0;
for (int i = 0; i < chessArray.length; i++) {
for (int i1 = 0; i1 < chessArray[i].length; i1++) {
if (chessArray[i][i1] != 0) {
sum++;
}
}
}
System.out.println("sum" + sum);
//创建稀疏数组
int sparseArr[][] = new int[sum + 1][3];
//给稀疏数组赋值
sparseArr[0][0] = 11;
sparseArr[0][1] = 11;
sparseArr[0][2] = sum;
int count = 0;
for (int i = 0; i < chessArray.length; i++) {
for (int i1 = 0; i1 < chessArray[i].length; i1++) {
if (chessArray[i][i1] != 0) {
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = i1;
sparseArr[count][2] = chessArray[i][i1];
}
}
}
System.out.println("得到的稀疏数组为");
for (int[] ints : sparseArr) {
System.out.printf("%d\t%d\t%d\t\n",ints[0],ints[1],ints[2]);
}
//稀疏数组转为二位数组
int chessArr[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
for (int i = 1; i <= sparseArr[0][2]; i++) {
chessArr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
for (int i = 0; i < chessArr.length; i++) {
for (int data : chessArr[i]) {
System.out.printf("%d\t",data);
}
System.out.println();
}
}
结果如下:
队列
- 队列是一个有序列表,可以用数组或者链表来实现
- 遵循先入先出的原则
rear是队列最后一个元素 ; front是队列最前面一个元素
可自行模拟数组模拟队列。
- 一定要考虑front和rear的值
- 要理解取模思想
当初始值 front = 0;rear = 0时
队列满的条件是: (rear +1 )% maxsize == front
队列为空的条件是:rear = front
队列有效的数据个数 (rear+maxsize - front)%maxsize
链表(LinkedList)
小结
- 链表是以节点的方式来存储的
- 每个节点包含data域存储数据,next域指向下一个节点
- 各个节点并不是连续存放的
- 链表分带点的链表和没有头节点的链表,根据实际的需求来确定
单链表
package main.com.wangxiang.queue;
/**
* @author Stone
* @date 2022/11/21
*/
public class SingleLingkedList {
public static void main(String[] args) {
HeroNode node1 = new HeroNode(1, "xxx", "aaa");
HeroNode node2 = new HeroNode(2, "xxx", "aaa");
HeroNode node3 = new HeroNode(3, "xxx", "aaa");
HeroNode node4 = new HeroNode(4, "xxx", "aaa");
HeroNode node5 = new HeroNode(5, "xxx", "aaa");
SingleLinkedList singleLinkedList = new SingleLinkedList();
// singleLinkedList.add(node1);
// singleLinkedList.add(node2);
// singleLinkedList.add(node3);
// singleLinkedList.add(node4);
// singleLinkedList.add(node5);
singleLinkedList.addByNo(node1);
singleLinkedList.addByNo(node4);
singleLinkedList.addByNo(node2);
singleLinkedList.addByNo(node2);
singleLinkedList.addByNo(node3);
singleLinkedList.list();
singleLinkedList.update(new HeroNode(3, "qqqq", "wwwww"));
singleLinkedList.list();
singleLinkedList.delete(new HeroNode(1,"",""));
singleLinkedList.list();
}
}
class SingleLinkedList {
private HeroNode head = new HeroNode(0, "", "");
//添加方法 , 不考虑顺序
public void add(HeroNode heroNode) {
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = heroNode;
}
//修改节点的信息,根据no编号来修改,即no编号不能改
public void update(HeroNode heroNode) {
if (head.next == null) {
System.out.println("链表为空");
return;
}
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (heroNode.no == temp.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = heroNode.name;
temp.nickName = heroNode.nickName;
} else {
System.out.println("没有找到该节点");
}
}
//删除方法
public void delete(HeroNode heroNode) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (heroNode.no == temp.next.no) {
flag = true;
break;
}
}
if (true) {
temp.next = temp.next.next;
}else {
System.out.println("未发现该节点");
}
}
//添加方法, 考虑顺序
public void addByNo(HeroNode heroNode) {
HeroNode temp = head;
Boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.println("编号已存在");
} else {
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//遍历
public void list() {
if (head.next == null) {
System.out.println("该链表为空");
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
class HeroNode {
public int no;
public String name;
public String nickName;
public HeroNode next;
//构造器
public HeroNode(int no, String name, String nickName) {
this.name = name;
this.no = no;
this.nickName = nickName;
}
//重写一下toString方法
@Override
public String toString() {
return "HeroNode[no=" + no + "name=" + name + "nickName=" + nickName + "]";
}
}
链表个人总结:
- 要注意链表的结构
- 链表做增删查改时要注意细节
- 要根据需求来思考是否需要头节点
头节点的作用:
- 防止单链表是空的而设的.当链表为空的时候,带头结点的头指针就指向头结点.如果当链表为空的时候,单链表没有带头结点,那么它的头指针就为NULL.
- 是为了方便单链表的特殊操作,插入在表头或者删除第一个结点.这样就保持了单链表操作的统一性!
- 单链表加上头结点之后,无论单链表是否为空,头指针始终指向头结点,因此空表和非空表的处理也统一了,方便了单链表的操作,也减少了程序的复杂性和出现bug的机会。
- 对单链表的多数操作应明确对哪个结点以及该结点的前驱。不带头结点的链表对首元结点、中间结点分别处理等;而带头结点的链表因为有头结点,首元结点、中间结点的操作相同 ,从而减少分支,使算法变得简单 ,流程清晰。对单链表进行插入、删除操作时,如果在首元结点之前插入或删除的是首元结点,不带头结点的单链表需改变头指针的值,在C 算法的函数形参表中头指针一般使用指针的指针(在C+ +中使用引用 &); 而带头结点的单链表不需改变头指针的值,函参数表中头结点使用指针变量即可。
栈:先入后出
双向链表
与单向链表的区别:
- 单向链表,查找方向只能是一个方向;双向链表可以向前,也可以向后查找
- 单向链表不能自我删除,需要靠辅助接点,而双向链表,则可以自我删除,所以前面所说的单向链表删除节点时,总是需要找到temp(为所查找节点的上一个节点)的下一个节点来删除
双向链表即指向下一个节点,也可指向前一个节点
约瑟夫问题
用一个不带头节点的环形链表来处理
案例:小孩出圈问题(此案例可自行去搜索)
栈
- 栈的英文为stack
- 栈是一个先入后出的有序列表
- 栈是限制线性表中的元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端为变化的一端,成为栈顶,另一端为固定的一段,称为栈底。
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的在栈顶,删除元素时相反,这便符合先入后出的原理。
- 出栈(pop)、入栈(push)
数组模拟栈示例:
package main.com.wangxiang.queue;
import java.util.Scanner;
import java.util.Stack;
/**
* @author wangxiang
* @date 2022/12/6
*/
public class StackDemo1 {
public static void main(String[] args) {
ArrayStack arrayStack = new ArrayStack(5);
String key = "";
boolean loop = true;
Scanner scanner = new Scanner(System.in);
while (loop) {
System.out.println("show:显示栈");
System.out.println("exit:推出");
System.out.println("push:入栈");
System.out.println("pop:出栈");
key = scanner.next();
switch (key) {
case "show":
arrayStack.list();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
arrayStack.push(value);
break;
case "pop":
try {
int res = arrayStack.pop();
System.out.printf("出栈的数据:%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop =false;
break;
default:
break;
}
}
System.out.println("程序推出了");
}
}
class ArrayStack {
private int maxSize;
private int[] stack;
private int top = -1;
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
public boolean isFull() {
return top == maxSize - 1;
}
public boolean isEmpty() {
return top == -1;
}
public void push(int value) {
if (isFull()) {
System.out.println("满了");
return;
}
top++;
stack[top] = value;
}
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈空");
}
int value = stack[top];
top--;
return value;
}
public void list() {
if (isEmpty()) {
System.out.println("栈空");
return;
}
for (int i = top; i >= 0; i--) {
System.out.printf("stack[%d] = %d\n", i, stack[i]);
}
}
}
使用栈计算出表达式结果 :3+2*6-2
前缀(波兰表达式)、中缀、后缀表达式(逆波兰表达式)
- 前缀表达式又称波兰表达式,前缀表达式的运算符位于操作数前
- 举例说名:(3+4)*5-6对应的前缀表达式是 - * + 3 4 5 6