目录
4.逆波兰计算器(这里输入的是后缀表达式,真正的计算器还应实现 中缀表达式转换为后缀表达式)
2.1 线性表的定义和特点
由n (n≥0)个数据特性相同的元素构成的有限序列称为线性表,(n=0)的时候被称为空表。
2.2 线性表的类型定义
2.3 线性表的顺序表示和实现
2.3.1 线性表的顺序表示
线性表的顺序存储又被称为顺序表
顺序存储表示:用一组地址连续的存储单元依次存储线性表的数据元素的方式,具有顺序存储结构的特点(数据间的逻辑关系和物理关系是一致的)
假设线性表 L 存储的起始位置为 LOC(A) ,sizeof(ElemType) 是每个数据元素所占用存储空间的大小,则表 L 所对应的顺序存储如下图:
由于线性表的长度可变,且所需最大存储空间随问题的不同而不同,在C语言中通常使用动态分配的一维数组表示线性表
//----- 顺序表的存储结构----- #define MAXSIZE 100 //顺序表可能达到的最大长度 typedef struct { ElemType *elem; //存储空间的基地址 int length; //当前长度 } SqList; //顺序表的结构类型为SqList
2.3.2 顺序表中基本操作的实现
一、顺序表初始化
①、为顺序表L动态分配一个预定义大小的数组空间,使elem指向这段空间的基地址。
②、将表的当前长度设为0。Status InitList(SqList &L) { //构造一个空的顺序表 L L.elem = new ElemType[MAXSIZE]; //为顺序表分配一个大小为MAXSIZE的数组空间,这是C++语句 if (!L.elem) exit(OVERFLOW); //存储分配失败退出 L.length = O; //空表长度为0 return OK; }
二、顺序表取值
①、判断指定的位置序号 i 值是否合理 (1≤i≤L.length), 若不合理,则返回ERROR。
②、若 i 值合理,则将第 i 个数据元素 L.elem[i-1] 赋给参数 e, 通过 e返回第 i 个数据元素的传值。
Status GetElem(SqList L, int i, ElemType &e) { if { (i < 1 || i > L.length) return ERROR; //判断l. 值是否合理,若不合理, 返回 ERROR e = L.elem[i - 1]; //elem[i-1] 单元存储第 i 个数据元素 return OK; } }
三、顺序表的按值查找
①、从第一个元素起,依次和 e相比较,若找到与 e相等的元素 L.elem[i], 则查找成功,返回该元素的序号 i+l (数组下标从 0 开始)
②、若查遍整个顺序表都没有找到,则查找失败, 返回0
int LocateELem(SqList L, ElemType e) { //在顺序表1中查找值为e的数据元素, 返回其序号 for (i = O; i < L.length; i++) { if (L.elem[i)==e){ return i + l; //查找成功, 返回序号 i+l } } return O; //查找失败, 返回 0 }
四、顺序表插入元素
在第 i 个位置插入一个元素时,需从最后一个元素即第 n 个元素开始,依次向后移动一个位置,直至第 i 个元素。
①、判断插入位置 i 是否合法(i 值的合法范围是 1≤i≤n+ I), 若不合法 则返回 ERROR。
②、判断顺序表的存储空间是否已满,若满则返回 ERROR。
③、将第n个至第 i 个位置的元素依次向后移动一个位置,空出第 i 个位置 ( i =n+l 时无需移动)。
④、将要插入的新元素e放入第i个位置。
⑤、表长加1
Status Listinsert(SqList &L, int i, ElemType e) { //在顺序表 L 中第 i 个位置之前插入新的元素 e, i值的合法范围是 1...i...L.length+l if ((i < l) || (i > L.length + l)) { return ERROR; //i值不合法 } if (L.length == MAXSIZE) { return ERROR; //当前存储空间已满 } for (j = L.length - 1; j >= i - 1; j--) { L.elem[j + l] = L.elem[j]; //插入位置及之后的元素后移 } L.elem[i - l] = e; //将新元素e放入第l个位置 ++L.length; //表长加1 return OK; }
五、顺序表删除元素
删除第 i 个元素时需将第 i+ 1 个至第 n 个元素依次向前移动一个位置 (i = n 时无需移动)
①、判断删除位置 i 是否合法(合法值为 1 ≤ i ≤n), 若不合法则返回 ERROR。
②、将第 i个至第n个的元素依次向前移动一个位置 (i = n时无需移动)
③、表长减 1
Status ListDelete(SqList &L, int i) { //在顺序表L中删除第J.个元素,J.值的合法范围是 1.;;i.;;L. length if ((i < l) || (i > L.length)) { return ERROR; //i值不合法 } for (j = i; j <= L.length - 1; j++) { L.elem[j - 1]=1.elem[j]; //被删除元素之后的元素前移 } --L.length; //表长减 1 return OK; }
2.4 线性表的链式表示和实现
链式存储结构的特点:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),存放数据元素的结点至少包括两个域(指针域、数据域),也可以包含若干个指针域和数据域。
2.4.1 单链表的定义和表示
关于带头结点的单链表及不带头节点的单链表
- 首元结点:是指链表中存储第一个数据元素的结点。
- 头结点:是在首元素结点之前附设的一个结点,其指针指向首元结点。
- 头指针:是指向链表中第一个结点的指针。若链表设有头结点,则头指针所指结点为线性表的头结点;若链表不设头结点,则头指针所指结点为该线性表的首元结点。
带头结点:
不带头结点:
- 带头结点的单链表为空时:L->next==NULL;
- 不带头结点的单链表为空时:L=NULL;
//----- 单链表的存储结构-----
typedef struct LNode {
ElemType data; //结点的数据域
struct LNode *next; //结点的指针域
} LNode, *LinkList; //LinkList 为指向结构体 LNode 的指针类型
2.4.2 单链表基本操作和实现
2.4.3 循环链表
2.4.3 双向链表
稀疏数组和队列
1.稀疏数组
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组
处理方法:
(1)记录数组一共有几行几列,有多少个不同的值
(2)把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
二维数组和稀疏数组 互相转换
public static void main(String[] args){
//二维数组展示为棋盘
int chessArr1[][] = new int[11][11];
chessArr1[1][2] = 1; //黑子
chessArr1[2][3] = 2; //蓝子
for(int [] row : chessArr1){
for(int data: row){
System.out.printf("%d\t",data);
}
System.out.println();
}
//二维数组 转 稀疏数组
//遍历二维数组得到非0数值的个数
int sum= 0;
for(int i =0; i < 11; i++){
for(int j =0; j < 11; j++){
if(chessArr1[i][j] != 0){
sum++;
}
}
}
//创建对应的稀疏数组
int sparseArr[][] = new int[sum+1][3];
sparseArr[0][0] = 11;
sparseArr[0][1] = 11;
sparseArr[0][2] = sum;
//将二维数组中不是0的值赋给稀疏数组
int count = 1; //用来记录是第几个不是零的值
for(int i =0; i < 11; i++){
for(int j =0; j < 11; j++){
if(chessArr1[i][j] != 0){
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] =chessArr[i][j];
}
}
}
//输出稀疏数组的形式
System.out.println();
for(int i = 0;i < sparseArr.length; i++){
System.out.println("%d\t%d\t%d\n", sparseArr[i][0],sparseArr[i][1],sparseArr[i][2])
System.out.println();
}
//稀疏数组 恢复成 二维数组
int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
for(int i = 1; i < sparseArr.length; i++){
chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i]][2];
}
}
2.队列
队列是一个有序列表,可以用数组或者链表来实现
遵循先入先出的原则数
1.数组模拟队列
class ArrayQueue{
private int maxSize;
private int front;
private int rear;
private int[] arr; //该数组用于存放数据,模拟队列
//队列构造器
public ArrayQueue(int arrMaxSize){
maxSize = arrMaxSize;
arr = new int[maxSize];
front = -1;
rear = -1;
}
//判断队列是否为满
public boolean isFull(){
return rear == maxSize - 1;
}
//判断队列是否为空
public boolean isEmpty(){
return rear == front;
}
//添加数据到队列
public void addQueue(int n){
if(isFull()){
System.out.println("队列已满,无法添加");
return;
}
rear++;
arr[rear] = n;
}
//获取数据出队列 只能获取队列最前面的
public int getQueue(){
if(isEmpty()){
System.out.println("队列空,无法获取");
}
front++;
return arr[front];
}
//显示队列所有数据 注意是显示不是不是取出
public void showQueue(){
if(isEmpty()){
System.out.println("队列空,没有数据显示");
return;
}
for(int i = 0; i < arr.length; i++){
System.out.printf("arr[%d] = %d\n", i, arr[i]);
}
}
//显示队列头部数据
public int headQueue(){
if(isEmpty()){
System.out.println("队列空,没有数据显示");
}
return arr[front + 1];
}
}
问题分析并优化
(1)目前数组使用一次就不能使用了
(2)将这个数组改成一个环形队队列,取模:%
class CircleArray{
private int maxSize;
//front 变量含义做一个调整:front 就指向第一个元素,而不是第一个元素前一个
private int front; //front = 0;
//rear 变量含义做一个调整: rear指向队列的最后一个位置的后一个位置,因为希望空出一个位置作为约定
private int rear; //rear = 0;
private int arr[];
//构造器
public CircleArray(int arrMaxSize){
maxSize = arrMaxSize;
arr = new int [maxSize];
}
//判断队列是否已满
public boolean isFull(){
return (rear + 1) % maxSize == front;
}
//判断队列是否为空
public boolean isEmpty(){
return rear == front;
}
public void addQueue(int n){
if(isFull()){
System.out.println("队列已满,无法添加");
return;
}
arr[rear] = n;
rear = (rear + 1) % maxSize; //让rear最后一个元素再后移一个位置
}
public int getQueue(){
if(isEmpty()){
throw new RuntimeException("队列空,无法取出数据");
}
//1.先把front对应的值保存到一个临时变量
//2.将front后移
//3.将临时保存的变量返回
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}
//求出当前队列有效存储位置的个数
public int getSize(){
return (rear + maxSize - front) % maxSize;
}
//显示队列所有数据
public void showQueue() {
if(isEmpty()){
throw new RuntimeException("队列空,无法取出数据");
}
for (int i = front; i < getSize() + front; i++){
System.out.printf("arr[%d]=%d", i % maxSize, arr[i % maxSize]);
}
}
//显示队列的头数据,注意不是取出数据
public int headQueue(){
if (isEmpty()){
throw new RuntimeException("队列空,没有数据");
}
return arr[front];
}
}
2.用链表模拟队列
链表是以节点的方式来存储的,每个节点都包含data域,next域:指向下一个节点
(1)双向链表
每个节点包含data域,next域,pre域(指向前一个节点)
(2)单向环形链表
应用场景:约瑟夫问题
例子:
class CircleSingleLinkedList {
//创建一个first节点
public Boy first = null;
//添加一个小孩节点,构成一个环形的链表
public void addBoy(int nums) {
//添加nums个节点来构建环形链表
if (nums < 1) {
System.out.println("nums的值不正确");
return;
}
Boy cur = null; //辅助指针
//使用for循环来构建环形链表
for (int i = 0; i <= nums; i++) {
Boy boy = new Boy(i);
//如果是只有一个节点的环形链表,特殊情况
if (i == 1) {
first = boy;
first.next = first;
cur = first;
} else {
cur.next = boy;
boy.next = first;
cur = boy;
}
}
}
栈(stack)
出栈(pop) 入栈(push)
1.栈的应用场景
2.用数组模拟栈的使用
class ArrayStack {
public int maxSize; //栈的大小
public int[] stack; //该数组用来模拟栈
public int top = -1; //表示栈顶,初始默认-1 表示没有值
//构造器 创建一个名为stack的数组 大小为maxSize用来模拟栈
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;
}
//push
public void push(int value) {
if(isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//pop
public int pop() {
if(isEmpty()){
throw new RuntimeException("栈空");
}
int value = stack[top];
top--;
return value;
}
//遍历栈 从栈顶开始
public void show() {
if(isEmpty()) {
System.out.println("没有数据");
return;
}
for(int i = top; i >= 0; i--) {
System.out.printf("stack[%d]=%d", i, stack[i]);
System.out.println();
}
}
}
前缀 中缀 后缀表达式
1.前缀(波兰表达式)
2.中缀
3.后缀(逆波兰表达式)
4.逆波兰计算器(这里输入的是后缀表达式,真正的计算器还应实现 中缀表达式转换为后缀表达式)
package com.tt12.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
//逆波兰计算器(从左往右扫描)
public class PolanNotation {
public static void main(String[] args) {
//(3+4)×5-6 => 3 4 + 5 × 6 -
//说明:为了方便,逆波兰表达式的数字和符号使用空格隔开
String suffixExpression = "3 4 + 5 × 6 -";
//1.先将suffixExpression放入ArrayList中
//2.将ArrayList 传递给一个方法,配合栈完成计算
List<String> list = getListString(suffixExpression);
System.out.println("rpnlist = " + list);
int res = calculate(list);
System.out.println(res);
}
//将逆波兰表达式放入ArrayList中的方法
public static List<String> getListString(String suffixExpression) {
//将suffixExpression分割
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<String>();
for(String ele: split) {
list.add(ele);
}
return list;
}
//完成计算的方法
public static int calculate(List<String> ls) {
Stack<String> stack = new Stack<String>();
for (String item : ls) {
//使用正则表达式
if (item.matches("\\d+")) { //匹配的是多位数
//直接入栈
stack.push(item);
} else {
//不是多位数是符号 则pop出两个数 再将运算结果入栈
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
int res = 0;
if (item.equals("+")) {
res = num1 + num2;
} else if (item.equals("-")) {
res = num2 - num1;
} else if (item.equals("×")) {
res = num1 * num2;
} else if (item.equals("÷")) {
res = num1 / num2;
}
else {
throw new RuntimeException("运算符有误");
}
System.out.println(res);
stack.push(String.valueOf(res));
}
}
//最后留在栈中的数据即答案
return Integer.parseInt(stack.pop());
}
}