数据结构与算法
数据结构与算法
数据结构包括:线性结构【数组、队列、链表、栈】+非线性结构
稀疏数组
应用场景
棋盘、地图 ===》二维数组的压缩
- 举例
demo
package com.summer.datastructure;
/**
* 稀疏数组
*/
public class SparseArray {
public static void main(String[] args) {
//创建一个原始的二维数组 8*8
// 0表示没有棋子 1:表示黑子 2:表示白子
int[][] chessArray = new int[8][8];
chessArray[1][2] = 1;
chessArray[2][3] = 2;
chessArray[4][5] = 2;
System.out.println("原始的二维数组");
for (int[] row :chessArray){
for (int i : row) {
System.out.print(i+" ");
}
System.out.println();
}
//将二位数组 转 稀疏数组的思想
//1、将二维数组遍历得到 非零 数 的个数
int sum = 0;
for (int[] row : chessArray){
for (int i : row) {
if (i != 0){
sum++;
}
}
}
//2、创建对应的稀疏数组
int[][] sparseArray = new int[sum+1][3];
sparseArray[0][0] = 8;
sparseArray[0][1] = 8;
sparseArray[0][2] = sum;
//遍历二维数组 将非零值 存放到稀疏数组
for(int i= 0;i<8;i++){
if (sum == 0){
break;
}
for (int j = 0; j < 8; j++) {
if (sum == 0){
break;
}
if (chessArray[i][j] != 0){
sparseArray[sum][0]=i;
sparseArray[sum][1]=j;
sparseArray[sum][2]=chessArray[i][j];
sum--;
}
}
}
//转换后的稀疏数组
System.out.println("转换为稀疏数组:");
for (int[] row:sparseArray){
for (int i:row){
System.out.print(i+" ");
}
System.out.println();
}
//将稀疏数组转换为二维数组
int[][] chessArrayCopy = new int[sparseArray[0][0]][sparseArray[0][1]];
for (int i = 1; i<=sparseArray[0][2];i++){
chessArrayCopy[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
}
System.out.println("稀疏数组恢复为二维数组:");
for (int[] ints : chessArrayCopy) {
for (int anInt : ints) {
System.out.print(anInt+" ");
}
System.out.println();
}
}
}
结果:
原始的二维数组
0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0
0 0 0 2 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 2 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
转换为稀疏数组:
8 8 3
4 5 2
2 3 2
1 2 1
稀疏数组恢复为二维数组:
0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0
0 0 0 2 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 2 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
练习
要求:【主要是IO操作】
1、在前面的基础上,将稀疏数组保存到磁盘上,比如map.data
2、恢复原来的数组时,读取map.data进行恢复【模拟存棋盘读棋盘】
队列
介绍
-
应用场景 ------》银行排队叫号系统
-
队列是一个有序列表,可以用
数组
或是链表
来实现。 -
遵循
先入先出
的原则,即:先存入队列的数据,要先取出。后存入的要后取出。
数组模拟队列
- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图所示,其中
maxSize是该队列的最大容量
。 - 因为队列的输出、输入是分别从前后端来处理的,因此需要两个变量
front及rear
分别记录队列的前后端的下标,front会随着数据输出而改变,rear则会随着数据输入而改变。
- 加入队列addQueue
- 将尾指针往后移:rear+1,当front==rear【队列空的】
- 若尾指针rear小于队列的最大下标maxSize-1,则数据存入rear所指的数组元素中,否则无法存入数据。rear == maxSize -1 【队列满了】
demo
package com.summer.datastructure.queue;
import java.util.Scanner;
public class ArrayQueueDemo {
public static void main(String[] args) {
ArrayQueue arrayQueue = new ArrayQueue(3);
Scanner scanner = new Scanner(System.in);
char key = ' ';//用户输入
boolean loop = true;
while(loop){
System.out.println("s(show):查看队列");
System.out.println("e(exit):退出程序");
System.out.println("a(add):添加数据到队列");
System.out.println("g(get):从队列中取数据");
System.out.println("h(head):查看队列头的数据");
key = scanner.next().charAt(0);
switch (key) {
case 's':
arrayQueue.show();
break;
case 'e':
scanner.close();//关闭
loop=false;
break;
case 'a':
System.out.println("请输入一个数字");
int n = scanner.nextInt();
arrayQueue.addQueue(n);
break;
case 'g':
try {
System.out.println("取出的数据是:"+arrayQueue.getQueue());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
System.out.println("队列头数据是:"+arrayQueue.headQueue());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
System.out.println("输入无效信息");
break;
}
}
System.out.println("程序退出");
}
}
//使用数组模拟队列---编写一个ArrayQueue的类
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;//指向队列头部,分析出front是指向队列头的前位置
rear = -1;//指向队列的尾,指向队列尾的数据【即就是队列的最后一个数据】
}
public boolean isFull(){
return rear == maxSize-1;
}
public boolean isEmpty(){
return front == rear;
}
//入队列
public void addQueue(int n){
if (isFull()){
System.out.println("队列已满,不能加入数据");
return;
}
arr[++rear] = n;
}
//出队列
public int getQueue(){
if (isEmpty()){
System.out.println("队列为空,无数据");
throw new RuntimeException("队列空,不能取数据");
}
return arr[++front];//由于队列头指向数据前一个位置,所以要++ 取数据
}
//显示队列所有数据
public void show(){
if (isEmpty()){
System.out.println("队列为空,没有数据");
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.printf("arr[%d] = %d",i,arr[i]);
System.out.println();
}
}
//显示队列的头的数是谁,注意不是取数据
public int headQueue(){
if (isEmpty()){
System.out.println("队列为空,无数据");
throw new RuntimeException("队列空,不能取数据");
}
return arr[front+1];
}
}
结果:
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
队列为空,没有数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0] = 10
arr[1] = 20
arr[2] = 0
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0] = 10
arr[1] = 20
arr[2] = 30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
40
队列已满,不能加入数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
h
队列头数据是:10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
h
队列头数据是:20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0] = 10
arr[1] = 20
arr[2] = 30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
队列为空,无数据
队列空,不能取数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
199
队列已满,不能加入数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
发现问题
- 目前数组使用一次就不能用了,没有达到复用的效果。
- 改进:使用算法将这个数组改成一个环形的数组,核心是用取模 %
数组环形队列
更新如下[环形队列数组]
1、front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素【front的初始默认为0】
2、rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定【rear初始值为0】
3、当队列满时,(rear + 1)% maxSize == front【满】
4、当队列空时,rear == front【空】
5、当我们这样分析,队列红有效的数据个数 (rear + maxSize -front) % maxSize
demo
package com.summer.datastructure.queue;
import java.util.Scanner;
public class CircleArrayQueueDemo {
public static void main(String[] args) {
System.out.println("测试数组模拟环形队列");
CircleArray circleArray = new CircleArray(4);//其队列的有效数据最大是3
Scanner scanner = new Scanner(System.in);
char key = ' ';//用户输入
boolean loop = true;
while(loop){
System.out.println("s(show):查看队列");
System.out.println("e(exit):退出程序");
System.out.println("a(add):添加数据到队列");
System.out.println("g(get):从队列中取数据");
System.out.println("h(head):查看队列头的数据");
key = scanner.next().charAt(0);
switch (key) {
case 's':
circleArray.show();
break;
case 'e':
scanner.close();//关闭
loop=false;
break;
case 'a':
System.out.println("请输入一个数字");
int n = scanner.nextInt();
circleArray.addQueue(n);
break;
case 'g':
try {
System.out.println("取出的数据是:"+circleArray.getQueue());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
System.out.println("队列头数据是:"+circleArray.headQueue());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
System.out.println("输入无效信息");
break;
}
}
System.out.println("程序退出");
}
}
class CircleArray{
private int maxSize;//数组最大容量
//front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素【front的初始默认为0】
private int front;
//rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定【rear初始值为0】
private int rear;
private int[] arr;//该数组用于存放数据,模拟队列
//创建队列的构造器
public CircleArray(int arrMaxSize){
maxSize = arrMaxSize;
arr = new int[maxSize];
// front = 0;
// rear = 0;
}
public boolean isFull(){
return (rear + 1) % maxSize == front;
}
public boolean isEmpty(){
return front == rear;
}
//入队列
public void addQueue(int n){
if (isFull()){
System.out.println("队列已满,不能加入数据");
return;
}
arr[rear] = n;
//将rear后移,这里必须考虑取模
rear = ++rear % maxSize;
}
//出队列
public int getQueue(){
if (isEmpty()){
System.out.println("队列为空,无数据");
throw new RuntimeException("队列空,不能取数据");
}
//这里分析出front是指向队列的第一个元素
//1、先把front对应的值保存到一个临时变量
//2、将front后移
//3、将临时保存的变量返回
int val = arr[front];
front =++front % maxSize;
return val;
}
//显示队列所有数据
public void show(){
if (isEmpty()){
System.out.println("队列为空,没有数据");
return;
}
//思路:从front开始遍历,遍历多少个元素
for (int i = front; i <front + size(); i++) {
System.out.printf("arr[%d]=%d\n",i%maxSize,arr[i%maxSize]);
}
}
//求出当前队列有效数据的个数
private int size(){
//rear = 2
//front = 1
//maxSize = 3
//size = 1
return (rear+maxSize-front)%maxSize;
}
//显示队列的头的数是谁,注意不是取数据
public int headQueue(){
if (isEmpty()){
System.out.println("队列为空,无数据");
throw new RuntimeException("队列空,不能取数据");
}
return arr[front];
}
}
结果:
测试数组模拟环形队列
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
队列为空,没有数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0]=10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0]=10
arr[1]=20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0]=10
arr[1]=20
arr[2]=30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
40
队列已满,不能加入数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[1]=20
arr[2]=30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
40
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[1]=20
arr[2]=30
arr[3]=40
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
50
队列已满,不能加入数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:40
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
队列为空,没有数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
队列为空,无数据
队列空,不能取数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
50
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
60
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
70
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0]=50
arr[1]=60
arr[2]=70
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:50
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[1]=60
arr[2]=70
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
90
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[1]=60
arr[2]=70
arr[3]=90
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:60
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
100
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[2]=70
arr[3]=90
arr[0]=100
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
e
程序退出
链表
介绍[Linked List]
- 链表是有序列表,但他在内存中是存储如下
小结:
1、链表是以节点的方式来存储
2、每个节点包含data域 和 next域:指向下一个节点
3、如图:发现链表的各个节点不一定是连续存放的
4、链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
单向链表的创建/添加
方法一:直接添加到链表尾部
思路
添加(创建):
1、先创建一个head头节点,作用就是表示单链表的头
2、后面我们每添加一个节点,就直接加入到链表的最后
遍历:
1、通过一个临时变量帮助遍历整个链表
demo
package com.summer.datastructure.linkedlist;
/**
* 单链表
*/
public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试:先创建几个节点
HeroNode h1 = new HeroNode(1,"宋江","及时雨");
HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
HeroNode h3 = new HeroNode(3,"吴用","智多星");
HeroNode h4 = new HeroNode(4,"林冲","豹子头");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(h1);
singleLinkedList.add(h4);
singleLinkedList.add(h3);
singleLinkedList.add(h2);
singleLinkedList.show();
}
}
/**
* 定义SingleLinkedList 管理我们的英雄
*/
class SingleLinkedList{
//初始化一个头节点 头节点不要动 不存放具体的数据
private HeroNode head = new HeroNode(0,"","");
//添加节点到单向链表
//思路:当不考虑编号的数据时
//1、找到当前链表的最后节点
//2、将最后这个节点的next 指向新的节点
public void add(HeroNode heroNode){
//因为head节点不能变,因此我们需要一个辅助变量temp
HeroNode temp = head;
//遍历链表,找到最后
while (true){
//找到链表的最后
if(temp.next == null){
break;
}
temp = temp.next;
}
//当退出while循环时 temp就指向了链表的最后
temp.next = heroNode;//将最后的节点指向新的节点
}
public void show(){
//判断链表是否为空
if (head.next == null){
System.out.println("链表为空");
return;
}
//因为头节点不能动 因此需要一个临时变量来遍历
HeroNode temp = head.next;
while(temp != null){
System.out.println(temp);
temp = temp.next;
}
System.out.println("显示结束");
}
}
/**
* 定义一个HeroNode 每个HeroNode 对象就是一个节点
*/
class HeroNode{
public int no;
public String name;
public String nickName;
public HeroNode next;//指向下一个节点
public HeroNode(int hNo,String hName,String hNickName){
this.no = hNo;
this.name = hName;
this.nickName = hNickName;
}
@Override
public String toString() {
return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
}
}
结果:
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 4 , name = 林冲 , nickName = 豹子头]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
显示结束
方法二:根据排名添加到指定位置
[添加时,如果有这个排名,则添加失败并给出提示]
思路
需要按照编号的顺序添加:
1、首先找到新添加的节点的位置,是通过辅助变量找到的[指针],通过遍历搞定
2、新的节点.next = temp.next
3、将temp.next = 新节点
demo
package com.summer.datastructure.linkedlist;
/**
* 单链表
*/
public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试:先创建几个节点
HeroNode h1 = new HeroNode(1,"宋江","及时雨");
HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
HeroNode h3 = new HeroNode(3,"吴用","智多星");
HeroNode h4 = new HeroNode(4,"林冲","豹子头");
SingleLinkedList singleLinkedList = new SingleLinkedList();
// singleLinkedList.add(h1);
// singleLinkedList.add(h4);
// singleLinkedList.add(h3);
// singleLinkedList.add(h2);
// singleLinkedList.show();
singleLinkedList.addByOrder(h1);
singleLinkedList.addByOrder(h4);
singleLinkedList.addByOrder(h3);
singleLinkedList.addByOrder(h2);
singleLinkedList.show();
}
}
/**
* 定义SingleLinkedList 管理我们的英雄
*/
class SingleLinkedList{
//初始化一个头节点 头节点不要动 不存放具体的数据
private HeroNode head = new HeroNode(0,"","");
//添加节点到单向链表
//思路:当不考虑编号的数据时
//1、找到当前链表的最后节点
//2、将最后这个节点的next 指向新的节点
public void add(HeroNode heroNode){
//因为head节点不能变,因此我们需要一个辅助变量temp
HeroNode temp = head;
//遍历链表,找到最后
while (true){
//找到链表的最后
if(temp.next == null){
break;
}
temp = temp.next;
}
//当退出while循环时 temp就指向了链表的最后
temp.next = heroNode;//将最后的节点指向新的节点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//[添加时,如果有这个排名,则添加失败并给出提示]
public void addByOrder(HeroNode heroNode){
//因为头节点不能动,因此我们需要一个辅助变量(指针)帮助我们找到添加的位置
//因为单链表,所以我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;//添加的编号是否存在 默认为false
while (true){
if (temp.next == null){
temp.next = heroNode;
break;
}
if(temp.next.no > heroNode.no){
heroNode.next = temp.next;
temp.next = heroNode;
break;
}else if (temp.next.no == heroNode.no){
System.out.println("ERROR---编号已经存在了");
break;
}
temp = temp.next;
}
}
public void show(){
//判断链表是否为空
if (head.next == null){
System.out.println("链表为空");
return;
}
//因为头节点不能动 因此需要一个临时变量来遍历
HeroNode temp = head.next;
while(temp != null){
System.out.println(temp);
temp = temp.next;
}
System.out.println("显示结束");
}
}
/**
* 定义一个HeroNode 每个HeroNode 对象就是一个节点
*/
class HeroNode{
public int no;
public String name;
public String nickName;
public HeroNode next;//指向下一个节点
public HeroNode(int hNo,String hName,String hNickName){
this.no = hNo;
this.name = hName;
this.nickName = hNickName;
}
@Override
public String toString() {
return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
}
}
结果:
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
HeroNode [no = 4 , name = 林冲 , nickName = 豹子头]
显示结束
public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试:先创建几个节点
HeroNode h1 = new HeroNode(1,"宋江","及时雨");
HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
HeroNode h3 = new HeroNode(3,"吴用","智多星");
HeroNode h4 = new HeroNode(4,"林冲","豹子头");
SingleLinkedList singleLinkedList = new SingleLinkedList();
// singleLinkedList.add(h1);
// singleLinkedList.add(h4);
// singleLinkedList.add(h3);
// singleLinkedList.add(h2);
// singleLinkedList.show();
singleLinkedList.addByOrder(h1);
singleLinkedList.addByOrder(h1);
singleLinkedList.addByOrder(h3);
singleLinkedList.addByOrder(h2);
singleLinkedList.show();
}
}
ERROR---编号已经存在了
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束
单向链表的更新
demo
主要看update方法
package com.summer.datastructure.linkedlist;
/**
* 单链表
*/
public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试:先创建几个节点
HeroNode h1 = new HeroNode(1,"宋江","及时雨");
HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
HeroNode h3 = new HeroNode(3,"吴用","智多星");
HeroNode h4 = new HeroNode(4,"林冲","豹子头");
SingleLinkedList singleLinkedList = new SingleLinkedList();
// singleLinkedList.add(h1);
// singleLinkedList.add(h4);
// singleLinkedList.add(h3);
// singleLinkedList.add(h2);
// singleLinkedList.show();
singleLinkedList.addByOrder(h1);
singleLinkedList.addByOrder(h1);
singleLinkedList.addByOrder(h3);
singleLinkedList.addByOrder(h3);
singleLinkedList.addByOrder(h2);
singleLinkedList.show();
System.out.println("------update------");
singleLinkedList.update(new HeroNode(1,"宋江11","及时雨11"));
singleLinkedList.show();
System.out.println("------update------");
singleLinkedList.update(new HeroNode(6,"Summer","come on"));
singleLinkedList.show();
}
}
/**
* 定义SingleLinkedList 管理我们的英雄
*/
class SingleLinkedList{
//初始化一个头节点 头节点不要动 不存放具体的数据
private HeroNode head = new HeroNode(0,"","");
//添加节点到单向链表
//思路:当不考虑编号的数据时
//1、找到当前链表的最后节点
//2、将最后这个节点的next 指向新的节点
public void add(HeroNode heroNode){
//因为head节点不能变,因此我们需要一个辅助变量temp
HeroNode temp = head;
//遍历链表,找到最后
while (true){
//找到链表的最后
if(temp.next == null){
break;
}
temp = temp.next;
}
//当退出while循环时 temp就指向了链表的最后
temp.next = heroNode;//将最后的节点指向新的节点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//[添加时,如果有这个排名,则添加失败并给出提示]
public void addByOrder(HeroNode heroNode){
//因为头节点不能动,因此我们需要一个辅助变量(指针)帮助我们找到添加的位置
//因为单链表,所以我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;//添加的编号是否存在 默认为false
while (true){
if (temp.next == null){
temp.next = heroNode;
break;
}
if(temp.next.no > heroNode.no){
heroNode.next = temp.next;
temp.next = heroNode;
break;
}else if (temp.next.no == heroNode.no){
System.out.println("ERROR---编号已经存在了");
break;
}
temp = temp.next;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改
//说明:
//根据newHeroNode 的 no 来修改即可
public void update(HeroNode newHeroNode){
boolean flag = false;
if(head.next == null){
System.out.println("链表为空");
return;
}
//先定义一个辅助变量
HeroNode temp = head;
while(temp.next != null){
if (temp.no == newHeroNode.no){
temp.nickName = newHeroNode.nickName;
temp.name = newHeroNode.name;
flag = true;
break;
}
temp = temp.next;
}
if (!flag){
System.out.println("链表中没有对应no的数据");
}
}
public void show(){
//判断链表是否为空
if (head.next == null){
System.out.println("链表为空");
return;
}
//因为头节点不能动 因此需要一个临时变量来遍历
HeroNode temp = head.next;
while(temp != null){
System.out.println(temp);
temp = temp.next;
}
System.out.println("显示结束");
}
}
/**
* 定义一个HeroNode 每个HeroNode 对象就是一个节点
*/
class HeroNode{
public int no;
public String name;
public String nickName;
public HeroNode next;//指向下一个节点
public HeroNode(int hNo,String hName,String hNickName){
this.no = hNo;
this.name = hName;
this.nickName = hNickName;
}
@Override
public String toString() {
return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
}
}
ERROR---编号已经存在了
ERROR---编号已经存在了
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束
------update------
HeroNode [no = 1 , name = 宋江11 , nickName = 及时雨11]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束
------update------
链表中没有对应no的数据
HeroNode [no = 1 , name = 宋江11 , nickName = 及时雨11]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束
Process finished with exit code 0
单向链表的删除
demo
主要看del方法
package com.summer.datastructure.linkedlist;
/**
* 单链表
*/
public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试:先创建几个节点
HeroNode h1 = new HeroNode(1,"宋江","及时雨");
HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
HeroNode h3 = new HeroNode(3,"吴用","智多星");
HeroNode h4 = new HeroNode(4,"林冲","豹子头");
SingleLinkedList singleLinkedList = new SingleLinkedList();
// singleLinkedList.add(h1);
// singleLinkedList.add(h4);
// singleLinkedList.add(h3);
// singleLinkedList.add(h2);
// singleLinkedList.show();
singleLinkedList.addByOrder(h1);
singleLinkedList.addByOrder(h1);
singleLinkedList.addByOrder(h3);
singleLinkedList.addByOrder(h3);
singleLinkedList.addByOrder(h2);
singleLinkedList.show();
System.out.println("------update------");
singleLinkedList.update(new HeroNode(1,"宋江11","及时雨11"));
singleLinkedList.show();
System.out.println("------update------");
singleLinkedList.update(new HeroNode(6,"Summer","come on"));
singleLinkedList.show();
System.out.println("------delete------");
singleLinkedList.del(1);
singleLinkedList.show();
singleLinkedList.del(3);
singleLinkedList.show();
singleLinkedList.del(3);
singleLinkedList.show();
singleLinkedList.del(2);
singleLinkedList.show();
singleLinkedList.del(2);
singleLinkedList.show();
}
}
/**
* 定义SingleLinkedList 管理我们的英雄
*/
class SingleLinkedList{
//初始化一个头节点 头节点不要动 不存放具体的数据
private HeroNode head = new HeroNode(0,"","");
//添加节点到单向链表
//思路:当不考虑编号的数据时
//1、找到当前链表的最后节点
//2、将最后这个节点的next 指向新的节点
public void add(HeroNode heroNode){
//因为head节点不能变,因此我们需要一个辅助变量temp
HeroNode temp = head;
//遍历链表,找到最后
while (true){
//找到链表的最后
if(temp.next == null){
break;
}
temp = temp.next;
}
//当退出while循环时 temp就指向了链表的最后
temp.next = heroNode;//将最后的节点指向新的节点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//[添加时,如果有这个排名,则添加失败并给出提示]
public void addByOrder(HeroNode heroNode){
//因为头节点不能动,因此我们需要一个辅助变量(指针)帮助我们找到添加的位置
//因为单链表,所以我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;//添加的编号是否存在 默认为false
while (true){
if (temp.next == null){
temp.next = heroNode;
break;
}
if(temp.next.no > heroNode.no){
heroNode.next = temp.next;
temp.next = heroNode;
break;
}else if (temp.next.no == heroNode.no){
System.out.println("ERROR---编号已经存在了");
break;
}
temp = temp.next;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改
//说明:
//根据newHeroNode 的 no 来修改即可
public void update(HeroNode newHeroNode){
boolean flag = false;
if(head.next == null){
System.out.println("链表为空");
return;
}
//先定义一个辅助变量
HeroNode temp = head;
while(temp.next != null){
if (temp.no == newHeroNode.no){
temp.nickName = newHeroNode.nickName;
temp.name = newHeroNode.name;
flag = true;
break;
}
temp = temp.next;
}
if (!flag){
System.out.println("链表中没有对应no的数据");
}
}
//删除节点
//思路
//1、head节点不能动 因此我们需要一个temp辅助节点找到待删除节点的前一个节点
//2、说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
public void del(int no){
HeroNode temp = head;
while (temp.next != null){
if (temp.next.no == no){
temp.next = temp.next.next;
return;
}
temp = temp.next;
}
}
public void show(){
//判断链表是否为空
if (head.next == null){
System.out.println("链表为空");
return;
}
//因为头节点不能动 因此需要一个临时变量来遍历
HeroNode temp = head.next;
while(temp != null){
System.out.println(temp);
temp = temp.next;
}
System.out.println("显示结束");
}
}
/**
* 定义一个HeroNode 每个HeroNode 对象就是一个节点
*/
class HeroNode{
public int no;
public String name;
public String nickName;
public HeroNode next;//指向下一个节点
public HeroNode(int hNo,String hName,String hNickName){
this.no = hNo;
this.name = hName;
this.nickName = hNickName;
}
@Override
public String toString() {
return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
}
}
------delete------
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
显示结束
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
显示结束
链表为空
链表为空
单链表面试题(新浪\百度\腾讯)
单链表的常见面试题有如下:
1、求单链表中有效节点的个数【遍历一遍链表即可】
//遍历单链表
public int linkedLength () {
HeroNode temp = head;
int i = 0;
while (temp.next != null){
i++;
temp = temp.next;
}
return i;
}
2、查找单链表中倒数第K个节点【新浪】
//思路:先求出链表长度 然后倒数改为正着数遍历
public HeroNode index(int k){
HeroNode temp = head;
if (k>linkedLength() || k<=0){
throw new RuntimeException("参数输入异常");
}
int i = linkedLength() + 1 - k;
for (int i1 = 0; i1 < i; i1++) {
temp = temp.next;
}
return temp;
}
3、单链表的反转【腾讯】
//思路
//1、先定义一个节点reverseHead = new HeroNode()
//2、从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表的reverseHead的最前端
//3、原来的链表的head.next = reverseHead.next
4、从尾到头打印单链表【百度:要求方式1:反向遍历 方式2:Stack栈】
5、合并两个有序的单链表,合并之后的链表依然有序【课后练习】
单向链表的缺点
- 单项链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
- 单项链表不能自我删除,需要靠辅助节点,而双向链表则可以自我删除,所以单向链表删除时节点,总是找到temp,temp是待删除节点的前一个节点来删除的。
双向链表
双向链表增删查改
demo
package com.summer.datastructure.linkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
System.out.println("双向链表的测试");
HeroNode2 h1 = new HeroNode2(1,"宋江","及时雨");
HeroNode2 h2 = new HeroNode2(2,"卢俊义","玉麒麟");
HeroNode2 h3 = new HeroNode2(3,"吴用","智多星");
HeroNode2 h4 = new HeroNode2(4,"林冲","豹子头");
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(h1);
doubleLinkedList.add(h2);
doubleLinkedList.add(h3);
doubleLinkedList.add(h4);
doubleLinkedList.list();
//修改
HeroNode2 newHeroNode2 = new HeroNode2(4,"公孙胜","入云龙");
doubleLinkedList.update(newHeroNode2);
System.out.println("修改过后的双向链表情况");
doubleLinkedList.list();
doubleLinkedList.del(3);
doubleLinkedList.del(4);
System.out.println("删除后双向链表情况");
doubleLinkedList.list();
}
}
//创建一个双向链表的类
class DoubleLinkedList{
//先初始化一个头节点,头节点不要动,不存放具体的数据
private HeroNode2 head = new HeroNode2(0,"","");
//返回头节点
public HeroNode2 getHead(){
return head;
}
//遍历双向链表的方法
public void list(){
if (head.next == null){
System.out.println("双向链表为空");
return;
}
HeroNode2 temp = head;
while (temp.next != null){
System.out.println(temp.next);
temp = temp.next;
}
}
//添加
public void add(HeroNode2 heroNode2){
HeroNode2 temp = head;
while (temp.next != null){
temp = temp.next;
}
//星辰
temp.next = heroNode2;
heroNode2.pre = temp;
}
//修改【可以看到双向链表的节点内容修改跟单向链表一样】
//只是节点的类型改成了HeroNode2
public void update(HeroNode2 heroNode2){
HeroNode2 temp = head;
while(temp != null){
if (temp.no == heroNode2.no){
temp.nickName = heroNode2.nickName;
temp.name = heroNode2.name;
return;
}
temp = temp.next;
}
System.out.println("链表中没有对应no的数据");
}
//从双向链表中删除节点
public void del(int id){
HeroNode2 temp = head;
while (temp != null){
if (temp.no == id){
temp.pre.next = temp.next;
//如果是最后一个节点 temp.next.pre会报空指针
if (temp.next!= null){
temp.next.pre = temp.pre;
}
return;
}
temp = temp.next;
}
System.out.println("没有对应id的节点");
}
}
/**
* 定义一个HeroNode 每个HeroNode 对象就是一个节点
*/
class HeroNode2{
public int no;
public String name;
public String nickName;
public HeroNode2 next;//指向下一个节点 默认为null
public HeroNode2 pre;//指向前一个节点 默认为null
public HeroNode2(int hNo,String hName,String hNickName){
this.no = hNo;
this.name = hName;
this.nickName = hNickName;
}
@Override
public String toString() {
return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
}
}
结果:
双向链表的测试
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
HeroNode [no = 4 , name = 林冲 , nickName = 豹子头]
修改过后的双向链表情况
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
HeroNode [no = 4 , name = 公孙胜 , nickName = 入云龙]
删除后双向链表情况
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
Process finished with exit code 0
单向环形链表的应用场景
【约瑟夫、约瑟夫环】问题
设定编号为1,2,.....n 的 n 个人围坐一圈,约定编号为 k (1<=k<=n) 的人从1开始报数,数到 m 的那个人出列,它的下一个人又开始从1报数,数到 m 的那个人 又出列,一次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示:
用一个不带头节点的循环链表来处理约瑟夫问题,先构成一个有 n 个节点的单循环链表,然后由 k 节点起从1开始计数,计到 m 时,对应节点从链表中删除,然后被删除的节点的下一个节点又从1开始计数,直到最后一个节点从链表中删除算法结束。
步骤一:
构建一个单向的环形链表思路:
1、先创建第一个节点,让first指向该节点,并形成环形
2、后面我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可
遍历环形链表:
1、先让一个辅助指针指向first节点
2、然后通过一个while循环遍历该环形链表即可 current.next == first 遍历结束
步骤二:
根据用户输入,生成一个小孩出圈的顺序
1、需要创建一个辅助指针helper,事先应该指向环形链表的最后这个节点
2、小孩报数前先让first和helper移动到k位置处
3、当小孩报数时,让first和helper同时移动m-1次(因为小孩自己也要报数)
4、这时就可以将first指向的小孩节点出圈
helper.next = first.next
first = first.next
原来first指向的节点就没有任何引用,就会被垃圾回收机制回收
demo
package com.summer.datastructure.linkedlist;
public class Josepfu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.list();
circleSingleLinkedList.countBoy(1,2,5);
}
}
//创建一个环形的单向链表
class CircleSingleLinkedList{
//创建一个first节点;
private Boy first;
//添加小孩节点
public void addBoy(int nums){
if (nums<1){
System.out.println("nums 的值不正确");
return;
}
Boy curBoy = null;//辅助指针,帮助构建环形链表
for (int i = 1; i <= nums; i++) {
//根据编号创建Boy节点
Boy boy = new Boy(i);
//如果是第一个小孩
if (i==1){
first = boy;
first.setNext(first);//构成环
curBoy = first;
}else {
curBoy.setNext(boy);
curBoy = boy;
boy.setNext(first);
}
}
}
//遍历单向环形链表
public void list(){
if (first == null){
System.out.println("单向环形链表为空");
return;
}
Boy temp = first;
while (temp != null){
System.out.println("编号:---"+temp.getNo());
if (temp.getNext() == first){
break;
}
temp = temp.getNext();
}
}
/**
* 根据用户的输入 小孩出圈的顺序
* @param startNo 表示从第几个节点开始报数
* @param countNum 报几个数
* @param nums 表示最由多少个小孩在圈中
*/
public void countBoy(int startNo,int countNum,int nums){
//先对数据进行校验
if (first == null || startNo <1 || startNo>nums){
System.out.println("参数输入有误,请重新输入");
return;
}
//先移动到要报数的位置
for (int i = 0; i < startNo-1; i++) {
first = first.getNext();
}
Boy helper = first;
//事先让helper指向first后面
while(helper != null){
if (helper.getNext() == first){//说明helper指向了最后的节点
break;
}
helper = helper.getNext();
}
//循环操作 让小孩出圈 直到圈中只有一个节点
while (true){
//只有一个节点
if (first == helper){
System.out.println("编号【"+first.getNo()+"】小孩 出圈");
System.out.println("----game over----");
break;
}
//移动m-1
for (int i = 0; i < countNum-1; i++) {
first = first.getNext();
helper = helper.getNext();
}
System.out.printf("编号【%d】小孩 出圈\n",first.getNo());
//出圈
first = first.getNext();
helper.setNext(first);
}
}
}
class Boy{
private int no;
private Boy next;
public Boy(int no){
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
结果:
编号:---1
编号:---2
编号:---3
编号:---4
编号:---5
编号【2】小孩 出圈
编号【4】小孩 出圈
编号【1】小孩 出圈
编号【5】小孩 出圈
编号【3】小孩 出圈
----game over----
栈stack
介绍
- 栈是一个先入后出的有序列表
- 栈是限制线性表中元素的插入和删除 只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
- 根据栈的定义可知,最先放入栈中的元素在栈底,最后放入元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
栈的应用场景
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
- 处理递归调用:和子程序的调用类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
- 表达式的转换 (中缀表达式转后缀表达式) 与求值(实际解决)
- 二叉树的遍历
- 图形的深度优先(depth-first)搜索法。
数组模拟栈
数组模拟栈的思路分析图
1、使用数组来模拟栈
2、定义一个top来表示栈顶,初始化为 -1
3、入栈的操作,当有数据加入到栈时,top++,stack[top] = data;
4、出栈的操作, int value = stack[top]; top-- ;return value;
demo
package com.summer.datastructure.linkedlist;
public class Josepfu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.list();
circleSingleLinkedList.countBoy(1,2,5);
}
}
//创建一个环形的单向链表
class CircleSingleLinkedList{
//创建一个first节点;
private Boy first;
//添加小孩节点
public void addBoy(int nums){
if (nums<1){
System.out.println("nums 的值不正确");
return;
}
Boy curBoy = null;//辅助指针,帮助构建环形链表
for (int i = 1; i <= nums; i++) {
//根据编号创建Boy节点
Boy boy = new Boy(i);
//如果是第一个小孩
if (i==1){
first = boy;
first.setNext(first);//构成环
curBoy = first;
}else {
curBoy.setNext(boy);
curBoy = boy;
boy.setNext(first);
}
}
}
//遍历单向环形链表
public void list(){
if (first == null){
System.out.println("单向环形链表为空");
return;
}
Boy temp = first;
while (temp != null){
System.out.println("编号:---"+temp.getNo());
if (temp.getNext() == first){
break;
}
temp = temp.getNext();
}
}
/**
* 根据用户的输入 小孩出圈的顺序
* @param startNo 表示从第几个节点开始报数
* @param countNum 报几个数
* @param nums 表示最由多少个小孩在圈中
*/
public void countBoy(int startNo,int countNum,int nums){
//先对数据进行校验
if (first == null || startNo <1 || startNo>nums){
System.out.println("参数输入有误,请重新输入");
return;
}
//先移动到要报数的位置
for (int i = 0; i < startNo-1; i++) {
first = first.getNext();
}
Boy helper = first;
//事先让helper指向first后面
while(helper != null){
if (helper.getNext() == first){//说明helper指向了最后的节点
break;
}
helper = helper.getNext();
}
//循环操作 让小孩出圈 直到圈中只有一个节点
while (true){
//只有一个节点
if (first == helper){
System.out.println("编号【"+first.getNo()+"】小孩 出圈");
System.out.println("----game over----");
break;
}
//移动m-1
for (int i = 0; i < countNum-1; i++) {
first = first.getNext();
helper = helper.getNext();
}
System.out.printf("编号【%d】小孩 出圈\n",first.getNo());
//出圈
first = first.getNext();
helper.setNext(first);
}
}
}
class Boy{
private int no;
private Boy next;
public Boy(int no){
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
结果
请出入你的选择
show
stack---index【2】data【30】
stack---index【1】data【20】
stack---index【0】data【10】
show:表示显示栈
exit:程序退出
push:表示入栈
pop:表示出栈
请出入你的选择
pop
出栈
30
show:表示显示栈
exit:程序退出
push:表示入栈
pop:表示出栈
请出入你的选择
pop
出栈
20
show:表示显示栈
exit:程序退出
push:表示入栈
pop:表示出栈
请出入你的选择
pop
出栈
10
show:表示显示栈
exit:程序退出
push:表示入栈
pop:表示出栈
请出入你的选择
pop
出栈
栈空
Exception in thread "main" java.lang.RuntimeException: 栈空,没有数据
at com.summer.datastructure.stack.ArrayStack.pop(ArrayStackDemo.java:76)
at com.summer.datastructure.stack.ArrayStackDemo.main(ArrayStackDemo.java:29)
Process finished with exit code 1
栈实现综合计算器
使用栈完成表达式的计算思路
1、通过一个index 值 [索引],用来遍历我们的表达式
2、如果我们发现是一个数字,就直接入数栈
3、如果发现扫描到的是一个符号,就分如下情况
3.1 如果发现当前的符号栈为空,就直接入栈
3.2 如果符号栈有操作符,就进行比较,如果当前的操作符的优先 级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到的结果,入数栈,然后将当前的操作符如符号栈,如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
4、当表达式扫描完成,就顺序的从数栈和符号栈中pop出相应的数和符号,并运算
5、最后在数栈只有一个数字,就是表达式的结果
demo
package com.summer.datastructure.stack;
public class Calculator {
public static void main(String[] args) {
// String expression = "3+2*6-2";
String expression = "30+20*6-2";
//创建两个栈 一个数栈 一个符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
//定义需要的相关变量
int index = 0;//用于扫描
int num1 = 0;
int num2 = 0;
int oper = 0;
int res = 0;
char ch = ' ';//将每次扫描得到的char保存到ch
String keepNum = "";//用于拼接多位数的
while (true){
if (index == expression.length()){
while (!operStack.isEmpty()){
num1 = numStack.pop();
num2 = numStack.pop();
int cal = numStack.cal(num1, num2, operStack.pop());
numStack.push(cal);
}
break;
}
//依次得到expression的每一个字符
ch = expression.substring(index,index+1).charAt(0);
if (operStack.isOper(ch)){//如果是运算符
if (operStack.isEmpty()){
operStack.push(ch);
}else {
int curOperPriority = operStack.priority(ch);
if (curOperPriority >= operStack.priority(operStack.peek())){
operStack.push(ch);
}else {
num1 = numStack.pop();
num2 = numStack.pop();
res = numStack.cal(num1,num2,operStack.pop());
numStack.push(res);
operStack.push(ch);
}
}
}else {
// numStack.push(ch -48);
//分析思路
//1、当初处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
//2、在处理数,需要向expression的表达式的index 后再看一位,如果是数就进行扫描,如果是符号才入栈
//3、因此我们需要定义一个变量字符串 用于拼接
//处理多位数
keepNum +=ch;
//判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入数栈
//如果ch已经是expression最后一位 直接入栈
if (index == expression.length() -1){
numStack.push(Integer.parseInt(keepNum));
}else {
if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))){// index 不要变只是往后看一位
//后一位是运算符 则入栈
numStack.push(Integer.parseInt(keepNum));
//!!!清空keepnum
keepNum = "";
}
}
}
index++;
}
System.out.printf("%s = %d \n",expression,numStack.pop());
}
}
//定义一个数组栈 需要扩展功能
class ArrayStack2{
private int maxSize;//栈的大小
private int[] stack;//数组,数组模拟栈,数据就放在该数组中
private int top = -1;//top表示栈顶,初始化为-1
public int getTop() {
return top;
}
//构造器
public ArrayStack2(int maxSize){
this.maxSize = maxSize;
stack = new int[maxSize];
}
public boolean isFull(){
return top == maxSize-1;
}
public boolean isEmpty(){
return top == -1;
}
public void push(int data){
if (isFull()){
System.out.println("栈满");
return;
}
top++;
stack[top] = data;
}
public int pop(){
if (isEmpty()){
System.out.println("栈空");
throw new RuntimeException("栈空,没有数据");//运行异常 不捕获也能抛出
}
int temp = stack[top];
top--;
return temp;
}
//遍历栈的时候 要从栈顶开始遍历
public void list(){
if (isEmpty()){
System.out.println("栈空,没有数据");
return;
}
for (int i = top; i >= 0; i--) {
System.out.printf("stack---index【%d】data【%d】\n",i,stack[i]);
}
}
//返回运算符的优先级,优先级是程序员确定的,优先级使用数字表示
//数字越大,则优先级越高
public int priority(int oper){
if (oper == '*' || oper == '/'){
return 1;
}else if (oper == '+' || oper == '-'){
return 0;
}else {
return -1;//假定目前的表达式只有 加减乘除
}
}
//判断是不是一个运算符
public boolean isOper(char val){
return val == '-' || val == '+' || val == '*' || val == '/';
}
//计算方法
public int cal(int num1,int num2,int oper){ ;
int result = 0;
if (oper == '+'){
result =num1 + num2;
}else if (oper == '-'){
result = num2 - num1;//注意顺序
}else if (oper == '*'){
result = num1 * num2;
}else if (oper == '/'){
result = num2 / num1;//注意顺序
}
return result;
}
public int peek(){
return stack[top];
}
public void reverse(){
int[] temp = new int[10];
for (int i = 0; i <= top; i++) {
temp[i] = stack[top - i];
}
for (int i = 0; i <= top; i++) {
stack[i] = temp[i];
}
}
}
结果
30+20*6-2 = 148
Process finished with exit code 0
前缀、中缀、后缀表达式
前缀表达式
-
前缀表达式又称波兰表达式,前缀表达式的运算符位于操作数之前
e.g.举例说明 (3+4)*5-6 对应的前缀表达式就是 - * + 3 4 5 6
前缀表达式的计算机求值过程
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程直到表达式的最左端,最后运算得出的值即为表达式的结果
例如:(3+4)*5-6 对应的前缀表达式就是 - * + 3 4 5 6 针对前缀表达式求值步骤如下:
1、从右至左扫描,将6 5 4 3压入堆栈
2、遇到 + 运算符,因此弹出3 4 3+4=7 7入栈
3、接下来* 7*5=35 35入栈
4、接下来- 35-6=29 即最终结果为29
中缀表达式
- 中缀表达式就是常见的运算表达式,如(3+4)*5 -6
- 中缀表达式的求值是我们人最熟悉的,但是对于计算机来说却不好操作(前面的那个计算机demo可以看出来这个问题哦),因此,在计算结果时,往往会将中缀表达式转成其他表达式来操作(一般转成后缀表达式)
后缀表达式
- 后缀表达式又称逆波兰表达式与前缀表达式相似,只是运算符位于操作数之后
- e.g. 举例说明 (3+4)*5 -6 对应的后缀表达式就是 3 4 + 5 * 6 -
后缀表达式的计算机求值过程
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素和栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。
e.g.例如 (3+4)*5 -6 对应的后缀表达式就是 3 4 + 5 * 6 - 针对后缀表达式求值步骤如下:
1、从左至右扫描,将3和4压入堆栈
2、遇到+ 3+4 = 7 7 入栈
3、遇到5入栈
4、遇到* 弹出5 和 7 5*7 = 35 35 入栈
5、遇到6 入栈
6、遇到- 弹出 6和35 用次顶元素35减去栈顶元素7 = 29 为最终结果
逆波兰计算器
demo
package com.summer.datastructure.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//先定义逆波兰表达式
//(3+4)*5 -6 对应的后缀表达式就是 3 4 + 5 * 6 -
//说明为了方便,逆波兰表达式的数字和符号使用空格 隔开
String suffixExpression = "3 4 + 5 * 6 -";
//思路
//1.先将"3 4 + 5 * 6 -"放到ArrayList中
//2.将ArrayList传递给一个方法,遍历ArrayList配合栈完成计算
List<String> rpnList = getListString(suffixExpression);
System.out.println(rpnList);
System.out.println("计算的结果--------> "+calculate(rpnList));
}
//将一逆波兰表达式,依次将数据和运算符 放入到ArrayList中
public static List<String> getListString(String suffixExpression){
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<>();
for (String s : split) {
list.add(s);
}
return list;
}
public static int calculate(List<String> ls){
//创建一个栈,只需要一个栈
Stack<String> stack = new Stack<>();
//遍历list
for (String item : ls) {
//这里使用正则表达式来取出数
if (item.matches("\\d+")){//匹配的时多位数
//直接入栈
stack.push(item);
}else {
//pop出两个数 并运算 ,运算结果在入栈
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
if (item.equals("+")){
res =num1+num2;
}else if (item.equals("*")){
res= num1*num2;
}else if (item.equals("/")){
res = num1/num2;
}else if(item.equals("-")){
res = num1-num2;
}else {
throw new RuntimeException("运算符有误");
}
stack.push(res+"");//把整数转换为字符串
}
}
//最后留在stack中的数据时运算的结果
return Integer.parseInt(stack.pop());
}
}
结果
[3, 4, +, 5, *, 6, -]
计算的结果--------> 29
Process finished with exit code 0
中缀表达式转后缀表达式步骤
- 步骤1:初始化两个栈:运算符栈s1和储存中间结果的栈s2
- 步骤2:从左至右扫描中缀表达式;
- 步骤3:遇到操作数时,将其压入s2
- 步骤4:遇到运算符时,比较其与s1栈顶运算符的优先级
- 4-1:如果s1为空,或者栈顶运算符为左括号“(”,则直接将此运算符入栈
- 4-2:否则,若优先级比栈顶运算符的高,也将运算符压入s1
- 4-3:否则,将s1栈顶的运算符弹出并压入到s2中,再次转到4-1与s1中新的栈顶运算符比较
- 步骤5:遇到括号时:
- 5-1:如果是左括号( ,直接压入s1
- 5-2:如果是右括号) ,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
- 步骤6:重复步骤2至5,直到表达式的最右边
- 步骤7:将s1中剩余的运算符依次弹出并压入s2
- 步骤8:依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的逆波兰表达式
e.g. 中缀表达式: 1+ ( (2 + 3) * 4 ) -5 --》 s1: - 5 + * 4 + 3 2 1 ---》 1 2 3 + 4 * + 5 -
说明:因为s2这个栈,在整个转换过程中,没有pop操作,而且后面我们还要逆序输出,因此s2可用ArrayList代替哦,实际问题自己灵活变通。
demo --- myself
package com.summer.datastructure.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//完成将一个中缀表达式转换成后缀表达式的功能
//说明
//1、1+ ( (2 + 3) * 4 ) -5 转成 1 2 3 + 4 * + 5 -
//2、1+ ( (2 + 3) * 4 ) -5 转换成一个ArrayList 好扫描
//3、将中缀表达式的list转换成后缀表达式的list
// String express = "1+((2+3)*4)-5";
String express = "(5-3)*5/2-(8-7)";
List<String> list = toInfixExressionList(express);
System.out.println(list);
List<String> suffixExpression = parseSuffixEpressionList(list);
List<String> suffixExpression1 = parseSuffixEpressionListV2(list);
System.out.println(suffixExpression);
System.out.println(suffixExpression1);
System.out.println(express +" self计算的结果--------> "+calculate(suffixExpression));
System.out.println(express +" v2计算的结果--------> "+calculate(suffixExpression1));
}
//即:1+ ( (2 + 3) * 4 ) -5 转成 1 2 3 + 4 * + 5 -
//将得到的中缀表达式list转换成后缀表达式list
public static List<String> parseSuffixEpressionList(List<String> ls){
Stack<String> stack = new Stack<>();
List<String> result = new ArrayList<>();
for(int i = 0;i<ls.size();i++){
if (isOper(ls.get(i))){
if (stack.empty()){
stack.push(ls.get(i));
}else {
String top = stack.pop();
if (top.equals("(")){
stack.push(top);
stack.push(ls.get(i));
}else {
if (rank(ls.get(i)) > rank(top)){
stack.push(top);
stack.push(ls.get(i));
}else {
result.add(top);
i--;
continue;
}
}
}
}else if (ls.get(i).equals("(") || ls.get(i).equals(")")){
if (ls.get(i).equals("(")){
stack.push(ls.get(i));
}else {
String temp = stack.pop();
while (!temp.equals("(")){
result.add(temp);
temp = stack.pop();
}
}
}else {
result.add(ls.get(i));
}
}
while (!stack.isEmpty()){
result.add(stack.pop());
}
// for (int i = result.size()-1; i >= 0; i--) {
// finalResult.add(result.get(i));
// }
return result;
}
//
public static List<String> parseSuffixEpressionListV2(List<String> ls){
Stack<String> s1 = new Stack<>();
List<String> s2 = new ArrayList<>();
for (String item : ls) {
if (item.matches("\\d+")){
s2.add(item);
}else if (item.equals("(")){
s1.push(item);
}else if (item.equals(")")){
while(!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();//将一对小括号弹出
}else {
//这里都是运算符
//当item<=栈顶运算符优先级 就把栈顶的运算符pop出来
while (!s1.isEmpty() && !s1.peek().equals("(") && Operation.getValue(item) <= Operation.getValue(s1.peek())){
s2.add(s1.pop());
}
s1.push(item);
}
}
//将s1加入s2中
while (!s1.isEmpty()){
s2.add(s1.pop());
}
return s2;
}
public static boolean isOper(String s){
if (s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")){
return true;
}
return false;
}
public static int rank(String a){
if (a.equals("+") || a.equals("-")){
return 1;
}else if (a.equals("*") || a.equals("/")){
return 2;
}
throw new RuntimeException("运算符优先级未添加");
}
//将中缀表达式抓花城对应的List
public static List<String> toInfixExressionList(String s){
List<String> list = new ArrayList<>();
int i = 0;
String str;
while (i< s.length()){
if (s.charAt(i) < 48 || s.charAt(i)>57){
list.add(s.charAt(i)+"");
i++;
}else {
str = "";
while (i<s.length() && s.charAt(i) >= 48 && s.charAt(i) <= 57){
str = "" + s.charAt(i);
i++;
}
list.add(str);
}
}
return list;
}
//将一逆波兰表达式,依次将数据和运算符 放入到ArrayList中
public static List<String> getListString(String suffixExpression){
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<>();
for (String s : split) {
list.add(s);
}
return list;
}
public static int calculate(List<String> ls){
//创建一个栈,只需要一个栈
Stack<String> stack = new Stack<>();
//遍历list
for (String item : ls) {
//这里使用正则表达式来取出数
if (item.matches("\\d+")){//匹配的时多位数
//直接入栈
stack.push(item);
}else {
//pop出两个数 并运算 ,运算结果在入栈
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
if (item.equals("+")){
res =num1+num2;
}else if (item.equals("*")){
res= num1*num2;
}else if (item.equals("/")){
res = num1/num2;
}else if(item.equals("-")){
res = num1-num2;
}else {
throw new RuntimeException("运算符有误");
}
stack.push(res+"");//把整数转换为字符串
}
}
//最后留在stack中的数据时运算的结果
return Integer.parseInt(stack.pop());
}
}
//编写一个类可以返回一个运算符对应的优先级
class Operation{
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 2;
private static int DIV = 2;
//写一个方法,返回对应的优先级数字
public static int getValue(String operation){
int result = 0;
switch (operation){
case "+":
result = ADD;
break;
case "-":
result = SUB;
break;
case "*":
result = MUL;
break;
case "/":
result = DIV;
break;
default:
throw new RuntimeException("运算符找不到");
}
return result;
}
}
结果:
[(, 5, -, 3, ), *, 5, /, 2, -, (, 8, -, 7, )]
[5, 3, -, 5, *, 2, /, 8, 7, -, -]
[5, 3, -, 5, *, 2, /, 8, 7, -, -]
(5-3)*5/2-(8-7) self计算的结果--------> 4
(5-3)*5/2-(8-7) v2计算的结果--------> 4
[1, +, (, (, 2, +, 3, ), *, 4, ), -, 5]
[1, 2, 3, +, 4, *, +, 5, -]
[1, 2, 3, +, 4, *, +, 5, -]
1+((2+3)*4)-5 self计算的结果--------> 16
1+((2+3)*4)-5 v2计算的结果--------> 16
Process finished with exit code 0
递归应用
概念
简单地说:递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让那个代码变得简洁。