对尚硅谷韩顺平老师所讲的数据结构和算法做了一个较完整的笔记。
包含:文字,图片,原代码。
个人看法:数据结构讲的比较通俗易懂,算法有一定难度不太建议初学者观看。
非线性结构包括:二维数组,多维数组,广义表,树结构,图结构
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
package hhhh;
import java.sql.Date;
public class SparseArray {
public static void main(String[] args) {
//创建一个原始的二维数组 11*11
//0:表示没有棋子, 1 表示 黑子 2 表示 蓝子
int chessArr1[][] = new int[11][11];
chessArr1[3][4]=1;
chessArr1[5][3]=2;
chessArr1[6][3]=2;
//输出原始的二维数组
System.out.println("原始的二维数组~~");
for(int[] row : chessArr1) {
for (int data : row) {
System.out.printf("%d\t",data);
}
System.out.println();
}
//将二维数组 转 稀疏数组的思路
//1.先遍历二维数组 得到非0数据的个数
int sum = 0;
for (int i = 0; i < chessArr1.length; i++) {
for (int j = 0; j < chessArr1.length; j++) {
if(chessArr1[i][j]!=0) {
sum++;
}
}
}
//2.创建对应的稀疏数组
int sparseArr[][] = new int[sum+1][3];
//给稀疏数组赋值
sparseArr[0][0]=11;
sparseArr[0][1]=11;
sparseArr[0][2]=sum;
//遍历二维数组,将非零的值存放到spareArr 中
int count = 0;//count 用于记录是第几个非0数据
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (chessArr1[i][j]!=0) {
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = chessArr1[i][j];
}
}
}
//输出稀疏数组的形式
System.out.println();
System.out.println("得到稀疏数组为~~");
for (int i = 0; i < sparseArr.length; i++) {
System.out.printf("%d\t%d\t%d\t\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]);
}
System.out.println();
//将稀疏数组恢复成原始的二维数组
//1.先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
//2.在读取稀疏数组后几行的数据(从第二行开始),并赋值给原始的二维数组即可
for (int i = 1; i < sparseArr.length; i++) {
chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
System.out.println();
System.out.println("恢复后的二维数组");
for (int[] row : chessArr2) {
for (int date : row) {
System.out.printf("%d\t",date);
}
System.out.println();
}
}
}
import java.util.Scanner;
public class zezhen11 {
public static void main(String[] args) {
//创建一个队列
ArrayQueue arrayQueue = new ArrayQueue(6);
char key = ' ';//接口用户输入
Scanner s = new Scanner(System.in);
boolean loop = true;
//输出菜单
while (loop){
System.out.println("s:显示队列");
System.out.println("e:退出程序");
System.out.println("a:添加数据到队列");
System.out.println("g:从队列取出数据");
System.out.println("h:查看队列头部数据");
key = s.next().charAt(0);//接受一个字符
switch (key){
case 's':
arrayQueue.showQueue();
break;
case 'a':
System.out.println("请输入一个数");
int value = s.nextInt();
arrayQueue.addQueue(value);
break;
case 'g':
try {
int res = arrayQueue.gerQueue();
System.out.println("去除的数据为:"+res);
}catch(Exception e){
System.out.println(e.getMessage());//捕抓异常信息
}
break;
case 'h':
try {
int res = arrayQueue.headQueue();
System.out.println("队列头数据为:"+res);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 'e':
s.close();
loop = false;
break;
default:
break;
}
}
}
}
//使用数组模拟队列
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 gerQueue() {
//判断队列是否为空
if (isEmpty()) {
//通过抛出异常来处理
throw new RuntimeException("队列空,不能取出");
}
front++;
return arr[front];
}
//显示队列
public void showQueue() {
//遍历
if (isEmpty()) {
System.out.println("队列为空");
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i] + "");
}
}
//显示队列的头数据
public int headQueue() {
//判断
if (isEmpty()) {
throw new RuntimeException("队列空");
} else {
return arr[front + 1];
}
}
}
对前面的数组模拟队列的优化,充分利用数组。因此将数组看做是一个环形。(通过取模的方式来实现即可)
package project;
import java.util.Scanner;
public class zezhen9 {
public static void main(String[] args) {
// 创建一个队列
CircleArray arrayQueue = new CircleArray(4);
char key = ' ';// 接口用户输入
Scanner s = new Scanner(System.in);
boolean loop = true;
// 输出菜单
while (loop) {
System.out.println("s:显示队列");
System.out.println("e:退出程序");
System.out.println("a:添加数据到队列");
System.out.println("g:从队列取出数据");
System.out.println("h:查看队列头部数据");
key = s.next().charAt(0);// 接受一个字符
switch (key) {
case 's':
arrayQueue.showQueue();
break;
case 'a':
System.out.println("请输入一个数");
int value = s.nextInt();
arrayQueue.addQueue(value);
break;
case 'g':
try {
int res = arrayQueue.gerQueue();
System.out.println("去除的数据为:" + res);
} catch (Exception e) {
System.out.println(e.getMessage());// 捕抓异常信息
}
break;
case 'h':
try {
int res = arrayQueue.headQueue();
System.out.println("队列头数据为:" + res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e':
s.close();
loop = false;
break;
default:
break;
}
}
}
}
// 使用数组模拟队列
class CircleArray {
private int maxSize;// 表达数组的最大容量
private int front;
// front变量的含义做一个调整;front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素
// front的初始值=0
private int rear;
// rear 变量的含义做一个调整;rear就指向队列的最后一个元素的后一个位置,因为希望空出一个空间数为约定
// rear的初始值=0
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 rear == front;
}
// 添加数据到队列
public void addQueue(int n) {
// 判断队列是否满
if (isFull()) {
System.out.println("队列满不能加入数据");
return;
}
// 直接将数据加入
arr[rear] = n;
// 将rear后移,这里必须考虑取模
rear = (rear + 1) % maxSize;
}
// 出队列
public int gerQueue() {
// 判断队列是否为空
if (isEmpty()) {
// 通过抛出异常来处理
throw new RuntimeException("队列空,不能取出");
}
// 这里需要分析出front是指向队列的第一个元素
// 1.先把front对应的值保留到一个临时变量
// 2.将front后移,考虑取模
// 3.将临时保存的变量返回
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}
// 显示队列
public void showQueue() {
// 遍历
if (isEmpty()) {
System.out.println("队列为空");
}
// 思路:从front开始遍历,遍历多少个元素
// 动脑筋
for (int i = front; i < front + size(); i++) {
System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
}
}
// 求当前队列有效数据的个数
public int size() {
return (rear + maxSize - front) % maxSize;
}
// 显示队列的头数据
public int headQueue() {
// 判断
if (isEmpty()) {
throw new RuntimeException("队列空");
} else {
return arr[front];
}
}
}
链表是有序的列表,但是它在内存中是存储如下
小结
单链表(带头结点) 逻辑结构示意图如下
第一步:在添加英雄时,直接添加到链表的尾部
代码实现
public class zezhen99 {
public static void main(String[] args){
//先创建节点
HeroNode heroNode1 = new HeroNode(1,"","");
HeroNode heroNode2 = new HeroNode(2,"","");
HeroNode heroNode3 = new HeroNode(3,"","");
//创建一个链表
SingleLikedlist singleLikedlist = new SingleLikedlist();
singleLikedlist.add(heroNode1);
singleLikedlist.add(heroNode2);
singleLikedlist.add(heroNode3);
singleLikedlist.showList();
}
}
//定义singleLinkedlist 管理我们的英雄
class SingleLikedlist{
//先初始化一个头节点
private HeroNode head = new HeroNode(0,"","");
//添加节点到单项节点
//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点next指向新节点
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助变量
HeroNode temp =head;
while (true){
if(temp.next == null){
break;
}
//没有到最后
temp = temp.next;
}
//当退出while循环,temp指向链表最后
//将最后这个节点的next指向新的节点
temp.next = heroNode;//将最后一个节点的next指向新加入的点
}
//显示链表
public void showList(){
//先判断链表是否为空
if(head.next == null){
return;
}
HeroNode temp = head.next;
while(true){
//是否到链表最后
if(temp == null){
break;
}
//输出节点信息
System.out.println(temp);
temp = temp.next;
}
}
}
//定义一个HeroNode,每一个HeroNode就是一个节点
class HeroNode{
//定义节点的私有属性 编号 姓名 外号 和next
public int no;
public String name;
public String nickname;
public HeroNode next;
//构造器
public HeroNode(){
}
public HeroNode(int no,String name, String nickname){
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写toString 方便显示
public String toString(){
return "Heronode [no= "+no+",name="+name+",nickname="+nickname+"]";
}
}
第二步:根据排名插入到指定位置
如果有这个排名,则添加失败,并给出提示
代码实现
public class zezhen99 {
public static void main(String[] args){
//先创建节点
HeroNode heroNode1 = new HeroNode(1,"","");
HeroNode heroNode2 = new HeroNode(2,"","");
HeroNode heroNode3 = new HeroNode(3,"","");
//创建一个链表
SingleLikedlist singleLikedlist = new SingleLikedlist();
singleLikedlist.addByOeder(heroNode3);
singleLikedlist.addByOeder(heroNode1);
singleLikedlist.addByOeder(heroNode2);
singleLikedlist.showList();
}
}
//定义singleLinkedlist 管理我们的英雄
class SingleLikedlist{
//先初始化一个头节点
private HeroNode head = new HeroNode(0,"","");
//添加节点到单项节点
//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点next指向新节点
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助变量
HeroNode temp =head;
while (true){
if(temp.next == null){
break;
}
//没有到最后
temp = temp.next;
}
//当退出while循环,temp指向链表最后
//将最后这个节点的next指向新的节点
temp.next = heroNode;//将最后一个节点的next指向新加入的点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//(如果有这个排名,则添加失败,并给出提示)
public void addByOeder(HeroNode heroNode){
//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
//因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;
while (true){
if (temp.next == null){//说明temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no){//位置找到,就在temp的后面插入
break;
}else if (temp.next.no == heroNode.no){//说明希望添加的heroNode的编号已然存在
flag = true;//说明编号存在
}
temp = temp.next;//后移,遍历当前链表
}
//判断flag的值
if(flag){
System.out.printf("准备插入的英雄的编号%d 已经存在了,不能加入\n",heroNode.no);
}else {
//插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//显示链表
public void showList(){
//先判断链表是否为空
if(head.next == null){
return;
}
HeroNode temp = head.next;
while(true){
//是否到链表最后
if(temp == null){
break;
}
//输出节点信息
System.out.println(temp);
temp = temp.next;
}
}
}
//定义一个HeroNode,每一个HeroNode就是一个节点
class HeroNode{
//定义节点的私有属性 编号 姓名 外号 和next
public int no;
public String name;
public String nickname;
public HeroNode next;
//构造器
public HeroNode(){
}
public HeroNode(int no,String name, String nickname){
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写toString 方便显示
public String toString(){
return "Heronode [no= "+no+",name="+name+",nickname="+nickname+"]";
}
}
第三步:修改节点
思路
代码实现
public class zezhen99 {
public static void main(String[] args){
//先创建节点
HeroNode heroNode1 = new HeroNode(1,"","");
HeroNode heroNode2 = new HeroNode(2,"","");
HeroNode heroNode3 = new HeroNode(3,"","");
HeroNode heroNode4 = new HeroNode(3,"zezhen","");
//创建一个链表
SingleLikedlist singleLikedlist = new SingleLikedlist();
singleLikedlist.addByOeder(heroNode3);
singleLikedlist.addByOeder(heroNode1);
singleLikedlist.addByOeder(heroNode2);
singleLikedlist.update(heroNode4);
singleLikedlist.showList();
}
}
//定义singleLinkedlist 管理我们的英雄
class SingleLikedlist{
//先初始化一个头节点
private HeroNode head = new HeroNode(0,"","");
//添加节点到单项节点
//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点next指向新节点
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助变量
HeroNode temp =head;
while (true){
if(temp.next == null){
break;
}
//没有到最后
temp = temp.next;
}
//当退出while循环,temp指向链表最后
//将最后这个节点的next指向新的节点
temp.next = heroNode;//将最后一个节点的next指向新加入的点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//(如果有这个排名,则添加失败,并给出提示)
public void addByOeder(HeroNode heroNode){
//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
//因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;
while (true){
if (temp.next == null){//说明temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no){//位置找到,就在temp的后面插入
break;
}else if (temp.next.no == heroNode.no){//说明希望添加的heroNode的编号已然存在
flag = true;//说明编号存在
}
temp = temp.next;//后移,遍历当前链表
}
//判断flag的值
if(flag){
System.out.printf("准备插入的英雄的编号%d 已经存在了,不能加入\n",heroNode.no);
}else {
//插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改
//说明
//1.根据 newHeroNode 的no来修改即可
public void update(HeroNode newheroNode){
//判断是否为空
if(head.next == null){
System.out.println("链表为空~");
return;
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false;//表示是否找到该节点
while (true){
if(temp == null){
break;//已经遍历完链表
}
if(temp.no == newheroNode.no){
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag 判断是否找到修改的节点
if(flag){
temp.name = newheroNode.name;
temp.nickname = newheroNode.nickname;
}else{//没有找到
System.out.printf("没有找到编号%d的节点,不能修改\n",newheroNode.no);
}
}
//显示链表[遍历]
public void showList(){
//先判断链表是否为空
if(head.next == null){
return;
}
HeroNode temp = head.next;
while(true){
//是否到链表最后
if(temp == null){
break;
}
//输出节点信息
System.out.println(temp);
temp = temp.next;
}
}
}
//定义一个HeroNode,每一个HeroNode就是一个节点
class HeroNode{
//定义节点的私有属性 编号 姓名 外号 和next
public int no;
public String name;
public String nickname;
public HeroNode next;
//构造器
public HeroNode(){
}
public HeroNode(int no,String name, String nickname){
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写toString 方便显示
public String toString(){
return "Heronode [no= "+no+",name="+name+",nickname="+nickname+"]";
}
}
第四步:删除节点
代码实现
public class zezhen99 {
public static void main(String[] args){
//先创建节点
HeroNode heroNode1 = new HeroNode(1,"","");
HeroNode heroNode2 = new HeroNode(2,"","");
HeroNode heroNode3 = new HeroNode(3,"","");
HeroNode heroNode4 = new HeroNode(3,"zezhen","");
//创建一个链表
SingleLikedlist singleLikedlist = new SingleLikedlist();
singleLikedlist.addByOeder(heroNode3);
singleLikedlist.addByOeder(heroNode1);
singleLikedlist.addByOeder(heroNode2);
singleLikedlist.update(heroNode4);
singleLikedlist.del(2);
singleLikedlist.showList();
}
}
//定义singleLinkedlist 管理我们的英雄
class SingleLikedlist{
//先初始化一个头节点
private HeroNode head = new HeroNode(0,"","");
//添加节点到单项节点
//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点next指向新节点
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助变量
HeroNode temp =head;
while (true){
if(temp.next == null){
break;
}
//没有到最后
temp = temp.next;
}
//当退出while循环,temp指向链表最后
//将最后这个节点的next指向新的节点
temp.next = heroNode;//将最后一个节点的next指向新加入的点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//(如果有这个排名,则添加失败,并给出提示)
public void addByOeder(HeroNode heroNode){
//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
//因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;
while (true){
if (temp.next == null){//说明temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no){//位置找到,就在temp的后面插入
break;
}else if (temp.next.no == heroNode.no){//说明希望添加的heroNode的编号已然存在
flag = true;//说明编号存在
}
temp = temp.next;//后移,遍历当前链表
}
//判断flag的值
if(flag){
System.out.printf("准备插入的英雄的编号%d 已经存在了,不能加入\n",heroNode.no);
}else {
//插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改
//说明
//1.根据 newHeroNode 的no来修改即可
public void update(HeroNode newheroNode){
//判断是否为空
if(head.next == null){
System.out.println("链表为空~");
return;
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false;//表示是否找到该节点
while (true){
if(temp == null){
break;//已经遍历完链表
}
if(temp.no == newheroNode.no){
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag 判断是否找到修改的节点
if(flag){
temp.name = newheroNode.name;
temp.nickname = newheroNode.nickname;
}else{//没有找到
System.out.printf("没有找到编号%d的节点,不能修改\n",newheroNode.no);
}
}
//删除节点
//思路
//1. head 不能动,因此我们需要一个temp辅助节点找到待删除的前一个节点
//2. 说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
public void del(int no){
HeroNode temp = head;
boolean flag = false;//标志是否找到待删除节点的
while(true){
if (temp.next == null){//已经到了链表的最后
break;
}
if (temp.next.no == no){
//找到的待删除节点的前一个节点temp
flag = true;
break;
}
temp = temp.next;//temp后移,遍历
}
//判断flag
if (flag){//找到
//可以删除
temp.next = temp.next.next;
}else{
System.out.printf("要删除的%d 节点不存在\n",no);
}
}
//显示链表[遍历]
public void showList(){
//先判断链表是否为空
if(head.next == null){
return;
}
HeroNode temp = head.next;
while(true){
//是否到链表最后
if(temp == null){
break;
}
//输出节点信息
System.out.println(temp);
temp = temp.next;
}
}
}
//定义一个HeroNode,每一个HeroNode就是一个节点
class HeroNode{
//定义节点的私有属性 编号 姓名 外号 和next
public int no;
public String name;
public String nickname;
public HeroNode next;
//构造器
public HeroNode(){
}
public HeroNode(int no,String name, String nickname){
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写toString 方便显示
public String toString(){
return "Heronode [no= "+no+",name="+name+",nickname="+nickname+"]";
}
}
求单链表中有效节点个数
public class zezhen99 {
public static void main(String[] args){
//先创建节点
HeroNode heroNode1 = new HeroNode(1,"","");
HeroNode heroNode2 = new HeroNode(2,"","");
HeroNode heroNode3 = new HeroNode(3,"","");
HeroNode heroNode4 = new HeroNode(3,"zezhen","");
//创建一个链表
SingleLikedlist singleLikedlist = new SingleLikedlist();
singleLikedlist.addByOeder(heroNode3);
singleLikedlist.addByOeder(heroNode1);
singleLikedlist.addByOeder(heroNode2);
singleLikedlist.update(heroNode4);
singleLikedlist.del(2);
singleLikedlist.showList();
System.out.println("有效的节点个数:"+getLength(singleLikedlist.getHead()));
}
//方法:获取到单链表的节点的个数(如果是带头节点的链表,需求不统计头节点)
public static int getLength(HeroNode head){
if (head.next == null){//空节点
return 0;
}
int length = 0;
//定义一个辅助的变量,这里没有统计头节点
HeroNode cur = head.next;
while (cur != null){
length++;
cur = cur.next;
}
return length;
}
}
//定义singleLinkedlist 管理我们的英雄
class SingleLikedlist{
//先初始化一个头节点
private HeroNode head = new HeroNode(0,"","");
//返回头节点
public HeroNode getHead() {
return head;
}
//添加节点到单项节点
//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点next指向新节点
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助变量
HeroNode temp =head;
while (true){
if(temp.next == null){
break;
}
//没有到最后
temp = temp.next;
}
//当退出while循环,temp指向链表最后
//将最后这个节点的next指向新的节点
temp.next = heroNode;//将最后一个节点的next指向新加入的点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//(如果有这个排名,则添加失败,并给出提示)
public void addByOeder(HeroNode heroNode){
//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
//因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;
while (true){
if (temp.next == null){//说明temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no){//位置找到,就在temp的后面插入
break;
}else if (temp.next.no == heroNode.no){//说明希望添加的heroNode的编号已然存在
flag = true;//说明编号存在
}
temp = temp.next;//后移,遍历当前链表
}
//判断flag的值
if(flag){
System.out.printf("准备插入的英雄的编号%d 已经存在了,不能加入\n",heroNode.no);
}else {
//插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改
//说明
//1.根据 newHeroNode 的no来修改即可
public void update(HeroNode newheroNode){
//判断是否为空
if(head.next == null){
System.out.println("链表为空~");
return;
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false;//表示是否找到该节点
while (true){
if(temp == null){
break;//已经遍历完链表
}
if(temp.no == newheroNode.no){
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag 判断是否找到修改的节点
if(flag){
temp.name = newheroNode.name;
temp.nickname = newheroNode.nickname;
}else{//没有找到
System.out.printf("没有找到编号%d的节点,不能修改\n",newheroNode.no);
}
}
//删除节点
//思路
//1. head 不能动,因此我们需要一个temp辅助节点找到待删除的前一个节点
//2. 说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
public void del(int no){
HeroNode temp = head;
boolean flag = false;//标志是否找到待删除节点的
while(true){
if (temp.next == null){//已经到了链表的最后
break;
}
if (temp.next.no == no){
//找到的待删除节点的前一个节点temp
flag = true;
break;
}
temp = temp.next;//temp后移,遍历
}
//判断flag
if (flag){//找到
//可以删除
temp.next = temp.next.next;
}else{
System.out.printf("要删除的%d 节点不存在\n",no);
}
}
//显示链表[遍历]
public void showList(){
//先判断链表是否为空
if(head.next == null){
return;
}
HeroNode temp = head.next;
while(true){
//是否到链表最后
if(temp == null){
break;
}
//输出节点信息
System.out.println(temp);
temp = temp.next;
}
}
}
//定义一个HeroNode,每一个HeroNode就是一个节点
class HeroNode{
//定义节点的私有属性 编号 姓名 外号 和next
public int no;
public String name;
public String nickname;
public HeroNode next;
//构造器
public HeroNode(){
}
public HeroNode(int no,String name, String nickname){
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写toString 方便显示
public String toString(){
return "Heronode [no= "+no+",name="+name+",nickname="+nickname+"]";
}
}
求单链表的倒数第K个节点
public class zezhen99 {
public static void main(String[] args) {
//先创建节点
HeroNode heroNode1 = new HeroNode(1, "", "");
HeroNode heroNode2 = new HeroNode(2, "", "");
HeroNode heroNode3 = new HeroNode(3, "", "");
HeroNode heroNode4 = new HeroNode(3, "zezhen", "");
//创建一个链表
SingleLikedlist singleLikedlist = new SingleLikedlist();
singleLikedlist.addByOeder(heroNode3);
singleLikedlist.addByOeder(heroNode1);
singleLikedlist.addByOeder(heroNode2);
singleLikedlist.update(heroNode4);
singleLikedlist.del(2);
singleLikedlist.showList();
System.out.println("有效的节点个数:" + getLength(singleLikedlist.getHead()));
HeroNode res = findLastIndexNode(singleLikedlist.getHead(), 2);
System.out.println(res);
}
//查找单链表中的倒数第K个节点
//思路
//1.编写一个方法,接收head节点,同时接收一个index
//2.index 表示是倒数第index个节点
//3.先把链表从头到尾遍历,看到链表的总的长度getLength
//4.size后,我们从链表的第一个开始遍历(size-index)个,就可以看到
//5.如果找到了,则返回该节点,否则返回null
public static HeroNode findLastIndexNode(HeroNode head, int index) {
//判断如果链表为空,返回null
if (head.next == null) {
return null;//没有找到
}
//第一个遍历得到链表的长度(节点长度)
int size = getLength(head);
//第二次遍历 size-index 位置,就是我们倒数的第K个节点
//先做一个index的校验
if (index <= 0 || index > size) {
return null;
}
//定义给辅助变量,for循环定位到倒数的index
HeroNode cur = head.next;
for (int i = 0; i < size - index; i++) {
cur = cur.next;
}
return cur;
}
//方法:获取到单链表的节点的个数(如果是带头节点的链表,需求不统计头节点)
public static int getLength(HeroNode head) {
if (head.next == null) {//空节点
return 0;
}
int length = 0;
//定义一个辅助的变量,这里没有统计头节点
HeroNode cur = head.next;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}
}
//定义singleLinkedlist 管理我们的英雄
class SingleLikedlist {
//先初始化一个头节点
private HeroNode head = new HeroNode(0, "", "");
//返回头节点
public HeroNode getHead() {
return head;
}
//添加节点到单项节点
//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点next指向新节点
public void add(HeroNode heroNode) {
//因为head节点不能动,因此我们需要一个辅助变量
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
//没有到最后
temp = temp.next;
}
//当退出while循环,temp指向链表最后
//将最后这个节点的next指向新的节点
temp.next = heroNode;//将最后一个节点的next指向新加入的点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//(如果有这个排名,则添加失败,并给出提示)
public void addByOeder(HeroNode heroNode) {
//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
//因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {//说明temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no) {//位置找到,就在temp的后面插入
break;
} else if (temp.next.no == heroNode.no) {//说明希望添加的heroNode的编号已然存在
flag = true;//说明编号存在
}
temp = temp.next;//后移,遍历当前链表
}
//判断flag的值
if (flag) {
System.out.printf("准备插入的英雄的编号%d 已经存在了,不能加入\n", heroNode.no);
} else {
//插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改
//说明
//1.根据 newHeroNode 的no来修改即可
public void update(HeroNode newheroNode) {
//判断是否为空
if (head.next == null) {
System.out.println("链表为空~");
return;
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false;//表示是否找到该节点
while (true) {
if (temp == null) {
break;//已经遍历完链表
}
if (temp.no == newheroNode.no) {
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag 判断是否找到修改的节点
if (flag) {
temp.name = newheroNode.name;
temp.nickname = newheroNode.nickname;
} else {//没有找到
System.out.printf("没有找到编号%d的节点,不能修改\n", newheroNode.no);
}
}
//删除节点
//思路
//1. head 不能动,因此我们需要一个temp辅助节点找到待删除的前一个节点
//2. 说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
public void del(int no) {
HeroNode temp = head;
boolean flag = false;//标志是否找到待删除节点的
while (true) {
if (temp.next == null) {//已经到了链表的最后
break;
}
if (temp.next.no == no) {
//找到的待删除节点的前一个节点temp
flag = true;
break;
}
temp = temp.next;//temp后移,遍历
}
//判断flag
if (flag) {//找到
//可以删除
temp.next = temp.next.next;
} else {
System.out.printf("要删除的%d 节点不存在\n", no);
}
}
//显示链表[遍历]
public void showList() {
//先判断链表是否为空
if (head.next == null) {
return;
}
HeroNode temp = head.next;
while (true) {
//是否到链表最后
if (temp == null) {
break;
}
//输出节点信息
System.out.println(temp);
temp = temp.next;
}
}
}
//定义一个HeroNode,每一个HeroNode就是一个节点
class HeroNode {
//定义节点的私有属性 编号 姓名 外号 和next
public int no;
public String name;
public String nickname;
public HeroNode next;
//构造器
public HeroNode() {
}
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写toString 方便显示
public String toString() {
return "Heronode [no= " + no + ",name=" + name + ",nickname=" + nickname + "]";
}
}
反转链表,有点难度
public class zezhen99 {
public static void main(String[] args) {
//先创建节点
HeroNode heroNode1 = new HeroNode(1, "", "");
HeroNode heroNode2 = new HeroNode(2, "", "");
HeroNode heroNode3 = new HeroNode(3, "", "");
HeroNode heroNode4 = new HeroNode(3, "zezhen", "");
//创建一个链表
SingleLikedlist singleLikedlist = new SingleLikedlist();
singleLikedlist.addByOeder(heroNode3);
singleLikedlist.addByOeder(heroNode1);
singleLikedlist.addByOeder(heroNode2);
singleLikedlist.update(heroNode4);
singleLikedlist.del(2);
singleLikedlist.showList();
System.out.println("有效的节点个数:" + getLength(singleLikedlist.getHead()));
HeroNode res = findLastIndexNode(singleLikedlist.getHead(), 2);
System.out.println(res);
//测试单链表的反转
reversetList(singleLikedlist.getHead());
singleLikedlist.showList();
}
//将单链表反转
public static void reversetList(HeroNode head){
//如果当前链表为空,或者只有一个节点,无需反转,直接返回
if (head.next == null || head.next.next == null){
return ;
}
//定义一个辅助的指针(变量),帮助我们遍历原来的链表
HeroNode cur = head.next;
HeroNode next = null;// 指向当前节点(cur)的下一个节点
HeroNode reverseHead = new HeroNode(0,"","");
//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端
while (cur != null){
next = cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
reverseHead.next = cur;//将cur连接到新的链表上
cur = next;//让cur后移
}
//将head.next指向reverseHead.next,实现单链表的反转
head.next = reverseHead.next;
}
//查找单链表中的倒数第K个节点
//思路
//1.编写一个方法,接收head节点,同时接收一个index
//2.index 表示是倒数第index个节点
//3.先把链表从头到尾遍历,看到链表的总的长度getLength
//4.size后,我们从链表的第一个开始遍历(size-index)个,就可以看到
//5.如果找到了,则返回该节点,否则返回null
public static HeroNode findLastIndexNode(HeroNode head, int index) {
//判断如果链表为空,返回null
if (head.next == null) {
return null;//没有找到
}
//第一个遍历得到链表的长度(节点长度)
int size = getLength(head);
//第二次遍历 size-index 位置,就是我们倒数的第K个节点
//先做一个index的校验
if (index <= 0 || index > size) {
return null;
}
//定义给辅助变量,for循环定位到倒数的index
HeroNode cur = head.next;
for (int i = 0; i < size - index; i++) {
cur = cur.next;
}
return cur;
}
//方法:获取到单链表的节点的个数(如果是带头节点的链表,需求不统计头节点)
public static int getLength(HeroNode head) {
if (head.next == null) {//空节点
return 0;
}
int length = 0;
//定义一个辅助的变量,这里没有统计头节点
HeroNode cur = head.next;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}
}
//定义singleLinkedlist 管理我们的英雄
class SingleLikedlist {
//先初始化一个头节点
private HeroNode head = new HeroNode(0, "", "");
//返回头节点
public HeroNode getHead() {
return head;
}
//添加节点到单项节点
//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点next指向新节点
public void add(HeroNode heroNode) {
//因为head节点不能动,因此我们需要一个辅助变量
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
//没有到最后
temp = temp.next;
}
//当退出while循环,temp指向链表最后
//将最后这个节点的next指向新的节点
temp.next = heroNode;//将最后一个节点的next指向新加入的点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//(如果有这个排名,则添加失败,并给出提示)
public void addByOeder(HeroNode heroNode) {
//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
//因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {//说明temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no) {//位置找到,就在temp的后面插入
break;
} else if (temp.next.no == heroNode.no) {//说明希望添加的heroNode的编号已然存在
flag = true;//说明编号存在
}
temp = temp.next;//后移,遍历当前链表
}
//判断flag的值
if (flag) {
System.out.printf("准备插入的英雄的编号%d 已经存在了,不能加入\n", heroNode.no);
} else {
//插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改
//说明
//1.根据 newHeroNode 的no来修改即可
public void update(HeroNode newheroNode) {
//判断是否为空
if (head.next == null) {
System.out.println("链表为空~");
return;
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false;//表示是否找到该节点
while (true) {
if (temp == null) {
break;//已经遍历完链表
}
if (temp.no == newheroNode.no) {
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag 判断是否找到修改的节点
if (flag) {
temp.name = newheroNode.name;
temp.nickname = newheroNode.nickname;
} else {//没有找到
System.out.printf("没有找到编号%d的节点,不能修改\n", newheroNode.no);
}
}
//删除节点
//思路
//1. head 不能动,因此我们需要一个temp辅助节点找到待删除的前一个节点
//2. 说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
public void del(int no) {
HeroNode temp = head;
boolean flag = false;//标志是否找到待删除节点的
while (true) {
if (temp.next == null) {//已经到了链表的最后
break;
}
if (temp.next.no == no) {
//找到的待删除节点的前一个节点temp
flag = true;
break;
}
temp = temp.next;//temp后移,遍历
}
//判断flag
if (flag) {//找到
//可以删除
temp.next = temp.next.next;
} else {
System.out.printf("要删除的%d 节点不存在\n", no);
}
}
//显示链表[遍历]
public void showList() {
//先判断链表是否为空
if (head.next == null) {
return;
}
HeroNode temp = head.next;
while (true) {
//是否到链表最后
if (temp == null) {
break;
}
//输出节点信息
System.out.println(temp);
temp = temp.next;
}
}
}
//定义一个HeroNode,每一个HeroNode就是一个节点
class HeroNode {
//定义节点的私有属性 编号 姓名 外号 和next
public int no;
public String name;
public String nickname;
public HeroNode next;
//构造器
public HeroNode() {
}
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写toString 方便显示
public String toString() {
return "Heronode [no= " + no + ",name=" + name + ",nickname=" + nickname + "]";
}
}
从尾到头打印单链表(百度面试题)(运用到栈的知识)
import java.util.Stack;
public class zezhen99 {
public static void main(String[] args) {
//先创建节点
HeroNode heroNode1 = new HeroNode(1, "", "");
HeroNode heroNode2 = new HeroNode(2, "", "");
HeroNode heroNode3 = new HeroNode(3, "", "");
HeroNode heroNode4 = new HeroNode(3, "zezhen", "");
//创建一个链表
SingleLikedlist singleLikedlist = new SingleLikedlist();
singleLikedlist.addByOeder(heroNode3);
singleLikedlist.addByOeder(heroNode1);
singleLikedlist.addByOeder(heroNode2);
singleLikedlist.update(heroNode4);
singleLikedlist.del(2);
singleLikedlist.showList();
System.out.println("有效的节点个数:" + getLength(singleLikedlist.getHead()));
HeroNode res = findLastIndexNode(singleLikedlist.getHead(), 2);
System.out.println(res);
//测试单链表的反转
reversetList(singleLikedlist.getHead());
singleLikedlist.showList();
//逆序打印
reversePrint(singleLikedlist.getHead());
}
//使用方式2来逆序打印(栈)
public static void reversePrint(HeroNode head){
if (head.next == null){
return;
}
//创建要给一个栈,将各个节点压入栈
Stack<HeroNode> stack = new Stack<HeroNode>();
HeroNode cur = head.next;
//将链表的所有节点压入栈
while (cur != null){
stack.push(cur);
cur = cur.next;//cur后移,这样就可以压入下一个节点
}
//将栈中的节点进行打印,pop出栈
while (stack.size() >0){
System.out.println(stack.pop());
}
}
//将单链表反转
public static void reversetList(HeroNode head){
//如果当前链表为空,或者只有一个节点,无需反转,直接返回
if (head.next == null || head.next.next == null){
return ;
}
//定义一个辅助的指针(变量),帮助我们遍历原来的链表
HeroNode cur = head.next;
HeroNode next = null;// 指向当前节点(cur)的下一个节点
HeroNode reverseHead = new HeroNode(0,"","");
//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端
while (cur != null){
next = cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
reverseHead.next = cur;//将cur连接到新的链表上
cur = next;//让cur后移
}
//将head.next指向reverseHead.next,实现单链表的反转
head.next = reverseHead.next;
}
//查找单链表中的倒数第K个节点
//思路
//1.编写一个方法,接收head节点,同时接收一个index
//2.index 表示是倒数第index个节点
//3.先把链表从头到尾遍历,看到链表的总的长度getLength
//4.size后,我们从链表的第一个开始遍历(size-index)个,就可以看到
//5.如果找到了,则返回该节点,否则返回null
public static HeroNode findLastIndexNode(HeroNode head, int index) {
//判断如果链表为空,返回null
if (head.next == null) {
return null;//没有找到
}
//第一个遍历得到链表的长度(节点长度)
int size = getLength(head);
//第二次遍历 size-index 位置,就是我们倒数的第K个节点
//先做一个index的校验
if (index <= 0 || index > size) {
return null;
}
//定义给辅助变量,for循环定位到倒数的index
HeroNode cur = head.next;
for (int i = 0; i < size - index; i++) {
cur = cur.next;
}
return cur;
}
//方法:获取到单链表的节点的个数(如果是带头节点的链表,需求不统计头节点)
public static int getLength(HeroNode head) {
if (head.next == null) {//空节点
return 0;
}
int length = 0;
//定义一个辅助的变量,这里没有统计头节点
HeroNode cur = head.next;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}
}
//定义singleLinkedlist 管理我们的英雄
class SingleLikedlist {
//先初始化一个头节点
private HeroNode head = new HeroNode(0, "", "");
//返回头节点
public HeroNode getHead() {
return head;
}
//添加节点到单项节点
//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点next指向新节点
public void add(HeroNode heroNode) {
//因为head节点不能动,因此我们需要一个辅助变量
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
//没有到最后
temp = temp.next;
}
//当退出while循环,temp指向链表最后
//将最后这个节点的next指向新的节点
temp.next = heroNode;//将最后一个节点的next指向新加入的点
}
//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
//(如果有这个排名,则添加失败,并给出提示)
public void addByOeder(HeroNode heroNode) {
//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
//因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {//说明temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no) {//位置找到,就在temp的后面插入
break;
} else if (temp.next.no == heroNode.no) {//说明希望添加的heroNode的编号已然存在
flag = true;//说明编号存在
}
temp = temp.next;//后移,遍历当前链表
}
//判断flag的值
if (flag) {
System.out.printf("准备插入的英雄的编号%d 已经存在了,不能加入\n", heroNode.no);
} else {
//插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改
//说明
//1.根据 newHeroNode 的no来修改即可
public void update(HeroNode newheroNode) {
//判断是否为空
if (head.next == null) {
System.out.println("链表为空~");
return;
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false;//表示是否找到该节点
while (true) {
if (temp == null) {
break;//已经遍历完链表
}
if (temp.no == newheroNode.no) {
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag 判断是否找到修改的节点
if (flag) {
temp.name = newheroNode.name;
temp.nickname = newheroNode.nickname;
} else {//没有找到
System.out.printf("没有找到编号%d的节点,不能修改\n", newheroNode.no);
}
}
//删除节点
//思路
//1. head 不能动,因此我们需要一个temp辅助节点找到待删除的前一个节点
//2. 说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
public void del(int no) {
HeroNode temp = head;
boolean flag = false;//标志是否找到待删除节点的
while (true) {
if (temp.next == null) {//已经到了链表的最后
break;
}
if (temp.next.no == no) {
//找到的待删除节点的前一个节点temp
flag = true;
break;
}
temp = temp.next;//temp后移,遍历
}
//判断flag
if (flag) {//找到
//可以删除
temp.next = temp.next.next;
} else {
System.out.printf("要删除的%d 节点不存在\n", no);
}
}
//显示链表[遍历]
public void showList() {
//先判断链表是否为空
if (head.next == null) {
return;
}
HeroNode temp = head.next;
while (true) {
//是否到链表最后
if (temp == null) {
break;
}
//输出节点信息
System.out.println(temp);
temp = temp.next;
}
}
}
//定义一个HeroNode,每一个HeroNode就是一个节点
class HeroNode {
//定义节点的私有属性 编号 姓名 外号 和next
public int no;
public String name;
public String nickname;
public HeroNode next;
//构造器
public HeroNode() {
}
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写toString 方便显示
public String toString() {
return "Heronode [no= " + no + ",name=" + name + ",nickname=" + nickname + "]";
}
}
栈的初步了解利于理解第四步
import java.util.Stack;
public class zezhen6 {
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
//入栈
stack.add("jack");
stack.add("tom");
stack.add("smith");
//出栈
while (stack.size()>0){
System.out.println(stack.pop());
}
}
}
管理单向链表的缺点分析
双向链表的代码实现
public class zezhen6 {
public static void main(String[] args) {
//测试
System.out.println("双向链表的测试");
//先创建节点
HeroNode2 heroNode1 = new HeroNode2(1, "", "");
HeroNode2 heroNode2 = new HeroNode2(2, "", "");
HeroNode2 heroNode3 = new HeroNode2(3, "", "");
//创建一个双向链表
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(heroNode1);
doubleLinkedList.add(heroNode2);
doubleLinkedList.add(heroNode3);
doubleLinkedList.showList();
//修改
HeroNode2 newHeroNode = new HeroNode2(3, "zz", "798");
doubleLinkedList.update(newHeroNode);
System.out.println("修改后的链表情况");
doubleLinkedList.showList();
doubleLinkedList.del(1);
System.out.println("删除后");
doubleLinkedList.showList();
}
}
class DoubleLinkedList {
//先初始化一个头节点
private HeroNode2 head = new HeroNode2(0, "", "");
//返回头节点
public HeroNode2 getHead() {
return head;
}
//遍历双向链表的方法
public void showList() {
//先判断链表是否为空
if (head.next == null) {
return;
}
HeroNode2 temp = head.next;
while (true) {
//是否到链表最后
if (temp == null) {
break;
}
//输出节点信息
System.out.println(temp);
temp = temp.next;
}
}
//添加一个节点到双向链表的最后
public void add(HeroNode2 heroNode) {
//因为head节点不能动,因此我们需要一个辅助变量
HeroNode2 temp = head;
while (true) {
if (temp.next == null) {
break;
}
//没有到最后
temp = temp.next;
}
//当退出while循环,temp指向链表最后
//形成一个双向链表
temp.next = heroNode;
heroNode.pre = temp;
}
//修改一个节点内容
public void update(HeroNode2 newheroNode) {
//判断是否为空
if (head.next == null) {
System.out.println("链表为空~");
return;
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode2 temp = head.next;
boolean flag = false;//表示是否找到该节点
while (true) {
if (temp == null) {
break;//已经遍历完链表
}
if (temp.no == newheroNode.no) {
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag 判断是否找到修改的节点
if (flag) {
temp.name = newheroNode.name;
temp.nickname = newheroNode.nickname;
} else {//没有找到
System.out.printf("没有找到编号%d的节点,不能修改\n", newheroNode.no);
}
}
//从双向链表中删除一个节点
//说明1 对于双向链表,我们可以直接找到要删除的节点
//2找到后, 自我删除即可
public void del(int no) {
//判断当前链表为空
if (head.next == null) {//空链表
System.out.println("链表为空,无法删除");
return;
}
HeroNode2 temp = head.next;//辅助变量
boolean flag = false;//标志是否找到待删除节点的
while (true) {
if (temp == null) {//已经到了链表的最后
break;
}
if (temp.no == no) {
//找到的待删除节点temp
flag = true;
break;
}
temp = temp.next;//temp后移,遍历
}
//判断flag
if (flag) {//找到
//可以删除
temp.pre.next = temp.next;
//如果是最后一个节点,就不需要执行下面这句话,否则出现空指针
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.printf("要删除的%d 节点不存在\n", no);
}
}
}
//定义HeroNode2, 每个HeroNode 对象就是一个节点
class HeroNode2 {
//定义节点的私有属性 编号 姓名 外号 和next
public int no;
public String name;
public String nickname;
public HeroNode2 next;//指向下一个节点
public HeroNode2 pre;//指向前一个节点
//构造器
public HeroNode2() {
}
public HeroNode2(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写toString 方便显示
public String toString() {
return "Heronode [no= " + no + ",name=" + name + ",nickname=" + nickname + "]";
}
}
代码实现
import java.util.Scanner;
public class zezhen11 {
public static void main(String[] args) {
//测试一下
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.showBoy();
circleSingleLinkedList.countBoy(1,2,5);
}
}
//创建一个环形的单向链表
class CircleSingleLinkedList{
//创建一个first节点,当前没有编号
private Boy first = null;
//添加小孩节点,构建一个环形链表
public void addBoy(int nums){
//nums 做一个数据校验
if (nums < 1){
System.out.println("nums的值不正确");
return;
}
Boy curBoy = null;//辅助指针,帮助构建环形链表
//使用for来创建我们的环形链表
for (int i = 1; i <= nums; i++){
//根据编号,创建小孩节点
Boy boy = new Boy(i);
//如果是第一个小孩
if (i == 1){
first = boy;
first.setNext(first);//构成环
curBoy = first;//让curBoy指向第一个小孩
}else{
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
//遍历当前的环形链表
public void showBoy(){
//判断链表是否为空
if (first == null){
System.out.println("没有任何小孩~");
return;
}
//因为first不能动,因此我们仍然使用一个辅助指针完成遍历
Boy curBoy = first;
while (true){
System.out.printf("小孩的编号 %d \n", curBoy.getNo());
if (curBoy.getNext() == first){//说明已经遍历完毕
break;
}
curBoy = curBoy.getNext();//curBoy后移
}
}
public void countBoy(int startNo,int countNum,int nums){
//先对数据进行校验
if (first == null || startNo < 1 || startNo > nums){
System.out.println("参数输入有误,请重新输入");
return;
}
//创建要给辅助指针,帮助完成小孩出圈
Boy helper = first;
//需创建一个辅助指针(变量) helper,事先应该指向环形链表的最后这个节点
while (true){
if (helper.getNext() == first){//说明helper指向最后小孩节点
break;
}
helper = helper.getNext();
}
//小孩报数前,先让first 和 helper 移动k-1次
for (int j = 0; j<startNo-1;j++){
first = first.getNext();
helper = helper.getNext();
}
//当小孩报数时,让first和helper指针同时移动m-1次,然后出圈
//这里是一个循环操作,知道圈中只有一个节点
while (true){
if (helper == first){//说明圈中只有一个节点
break;
}
//这时first和helper指针同时移动countNum - 1
for(int j = 0; j < countNum - 1; j++){
first = first.getNext();
helper = helper.getNext();
}
//这时first指向的节点,就是要出圈小孩的节点
System.out.printf("小孩%d出圈\n",first.getNo());
//这时将first指向的小孩节点出圈
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最后留在圈中的小孩编号%d\n",first.getNo());
}
}
//创建一个Boy类,表示一个节点
class Boy{
private int no;//编号
private Boy next;//指向下一个节点,默认null
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;
}
}
代码实现
import java.util.Scanner;
public class ArrayStackDemo {
public static void main(String[] args) {
//测试一下ArrayStack 是否正确
//先创建一个ArrayStack对象->表示栈
ArrayStack stack = new ArrayStack(4);
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: 表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key = scanner.next();
switch (key) {
case "show":
stack.list();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
int res = stack.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("程序退出~~");
}
}
//定义一个ArrayStack 表示栈
class ArrayStack {
private int maxSize;//栈的大小
private int[] stack;//数组,数组模拟栈,数据就放在该数组
private int top = -1;//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;
}
//入栈-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 list() {
if (isEmpty()) {
System.out.println("栈空,没有数据~~");
return;
}
//需要从栈顶开始显示数据
for (int i = top; i >= 0; i--) {
System.out.printf("stack[%d]=%d\n",i, stack[i]);
}
}
}
代码实现[1.先实现一位数的运算,2.扩展到多位数的运算]
public class zezhen10 {
public static void main(String[] args) {
// 根据前面老师思路,完成表达式的运算
String expression = "70+2*6-2";
// 创建两个栈,数栈,一个符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
// 定义需要的相关变量
int index = 0;// 用于扫描
int num1 = 0, num2 = 0;
int oper = 0;
int res = 0;
char ch = ' ';// 将每次扫描得到char保存到ch
String keepNum = "";//用于拼接多位数
// 开始while循环的扫描expression
while (true) {
// 依次得到expression 的每一个字符
ch = expression.substring(index, index + 1).charAt(0);
// 判断当前ch是什么,然后做相应的处理
if (operStack.isOper(ch)) {
// 判断当前的符号栈是否为空
if (!operStack.isEmpty()) {
// 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数
// 在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈
if (operStack.priority(ch) <= operStack.priority(operStack.peek()) ) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = operStack.cal(num1, num2, oper);
// 把运算的结果如数栈
numStack.push(res);
// 然后将当前的操作符入符号栈
operStack.push(ch);
} else {
// 如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
operStack.push(ch);
}
} else {
// 如果为空直接入符号栈
operStack.push(ch);
}
} else {//如果是数,则直接入数栈
//1.当处理多位数时,不能发现是一个数就立即入栈,因为·他可能是多位数
//2.在处理数时,需要向expression的表达式的index后再看一位,如果是数就进行扫描,如果是符号才入栈
//3. 因此我们需要定义一个变量字符串,用于拼接
//numStack.push(ch-48);
//处理多位数
keepNum += ch;
//如果ch已经是expression的最后一位,就直接入栈
if (index == expression.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else {
//判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈
//注意是看后一位,不是index++
if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))){
//如果后一位是运算符,则入栈keepNum = "1"或者"123"
numStack.push(Integer.parseInt(keepNum));
//重要的!!!!!!,keepNum清空
keepNum = "";
}
}
}
//让index+1,并判断是否扫描到expression最后
index++;
if(index >= expression.length()) {
break;
}
}
//当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行,
while(true) {
//如果符号栈为空,则计算到最后的结果,数栈中只有一个数字[结果]
if(operStack.isEmpty()) {
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = operStack.cal(num1, num2, oper);
numStack.push(res);
}
//将数栈的最后数,pop出,就是结果
System.out.printf("表达式: %s = %d",expression,numStack.pop());
}
}
//先创建一个栈,直接使用前面创建好的
//定义一个ArrayStack2 表示栈,需要扩展功能
class ArrayStack2 {
private int maxSize;// 栈的大小
private int[] stack;// 数组,数组模拟栈,数据就放在该数组
private int top = -1;// top表示栈顶,初始化为-1
// 构造器
public ArrayStack2(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
// 增加一个方法,可以返回当前栈顶的值,但不是真正的pop
public int peek() {
return stack[top];
}
// 栈满
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 list() {
if (isEmpty()) {
System.out.println("栈空,没有数据~~");
return;
}
// 需要从栈顶开始显示数据
for (int i = top; i >= 0; i--) {
System.out.printf("stack[%d]=%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 res = 0;// res 用于存放计算的结果
switch (oper) {
case '+':
res = num1 + num2;
break;
case '-':
res = num2 - num1;
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num2 / num1;
break;
default:
break;
}
return res;
}
}
我们完成一个逆波兰计算器,要求完成如下任务:
代码实现
import java.awt.*;
import java.time.chrono.ThaiBuddhistChronology;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class zz {
public static void main(String[] args) {
//先定义个逆波兰表达式
//(3+4)*5-6 => 3 4 + 5 * 6 -
//说明为了方便,逆波兰表达式的数字和符号使用空格隔开
String suffixEepression = "3 4 + 5 * 6 -";
//思路
//1.先将"3 4 + 5 * 6 - " => 放到ArrayList中
//2.将ArrayList 传递给一个方法,遍历ArrayList 配合栈 完成计算
List<String> list = getListString(suffixEepression);
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>();
//遍历 ls
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("运算符有误");
}
//把res 入栈
stack.push(""+res);//把一个整数转成字符串并加入栈
}
}
//最后留在stack中的数据是运算结果
return Integer.parseInt(stack.pop()) ;
}
}
思路理解
大家看到,后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。
代码实现
import java.awt.*;
import java.time.chrono.ThaiBuddhistChronology;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class Test1 {
public static void main(String[] args) {
//完成将一个中缀表达式转成后缀表达式的功能
//说明
//1. 1+((2+3)*4)-5 => 转成 1 2 3 + 4 * + 5 -
//2.因为直接对string进行操作,不方便,因此先将“1+((2+3)*4)-5" => 中缀的表达式对应的List
// 即“ 1+((2+3)*4)-5” => ArrayList [1,+,(,(,2,+3,),*,4,),-,5]
String expression = "1+((2+3)*4)-5";
List<String> infixExpressionList = toInfixExpressionList(expression);
System.out.println("中缀表达式对应的List" + infixExpressionList);
List<String> SuffixExpressionList = parseSuffixExpressionList(infixExpressionList);
System.out.println("后缀表达式对应的List" + SuffixExpressionList);
System.out.printf("expression=%d", calculate(SuffixExpressionList));
System.out.println();
//先定义个逆波兰表达式
//(3+4)*5-6 => 3 4 + 5 * 6 -
//说明为了方便,逆波兰表达式的数字和符号使用空格隔开
String suffixEepression = "3 4 + 5 * 6 -";
//思路
//1.先将"3 4 + 5 * 6 - " => 放到ArrayList中
//2.将ArrayList 传递给一个方法,遍历ArrayList 配合栈 完成计算
List<String> list = getListString(suffixEepression);
System.out.println("rpnList" + list);
int res = calculate(list);
System.out.println("计算的结果是=" + res);
}
//即Arraylist [1,+,(,(,2,+3,),*,4,),-,5] => ArrayList [1,2,3,+,4,*,+,5,-]
//方法:将得到的中缀表达式对应的list => 后缀表达式对应的list
public static List<String> parseSuffixExpressionList(List<String> ls){
//定义两个栈
Stack<String> s1 = new Stack<String>();//符号栈
//说明:因为s2这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出
//所以不用stack而用list
List<String> s2 = new ArrayList<String>();//储存中间结果的lists2
//遍历ls
for (String item: ls){
//如果是一个数,加入s2
if (item.matches("\\d+")){
s2.add(item);
}else if (item.equals("(")){
s1.push(item);
}else if (item.equals(")")){
//如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这对括号丢弃
while (!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();//将(弹出s1栈,消除小括号
}else{
//优先级比较
while (s1.size() !=0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
s2.add(s1.pop());
}
//还需要将item压入栈
s1.push(item);
}
}
//将s1中剩余的运算符依次弹出并加入s2
while (s1.size() != 0){
s2.add(s1.pop());
}
return s2;//注意因为是存放到list,因此按顺序输出就是对应的后缀表达式对应的List
}
//方法:将中缀表达式转成对应的List
// s = "1+((2+3)*4)-5";
public static List<String> toInfixExpressionList(String s) {
//定义一个List,存放中缀表达式对应的内容
List<String> ls = new ArrayList<String>();
int i = 0;//这时是一个指针,用于遍历中缀表达式字符串
String str;//对多位数的拼接
char c; //每遍历到一个字符,就放到c
do {
//如果c是一个非数字,我需要加入到ls
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
ls.add("" + c);
i++;//i需要后移
} else {//如果是一个数,需要考虑多位数
str = "";//先将str置成“”
while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){
str += c ;//拼接
i++;
}
ls.add(str);
}
} while (i < s.length());
return ls;//返回
}
//将一个逆波兰表达式,依次将数据和运算符放入到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>();
//遍历 ls
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("运算符有误");
}
//把res 入栈
stack.push("" + res);//把一个整数转成字符串并加入栈
}
}
//最后留在stack中的数据是运算结果
return Integer.parseInt(stack.pop());
}
}
//编写一个类Operation可以返回一个运算符对应的优先级
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:
System.out.println("不存在该运算符");
}
return result;
}
}
简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂的问题,同时可以让代码变得简洁。
若加入else,则输出一个2。
package com.atguigu.recusion;
public class RecusionTest01 {
public static void main(String[] args) {
//通过打印问题,回顾递归的调用机制
test(4);
}
public static void test(int n){
if(n > 2){
test(n -1);
}else {
System.out.println("n = " + n);
}
}
}
package com.atguigu.recusion;
public class RecusionTest01 {
public static void main(String[] args) {
//通过打印问题,回顾递归的调用机制
test(4);
System.out.println("res + " + factorial(2));
}
public static void test(int n){
if(n > 2){
test(n -1);
}
System.out.println("n = " + n);
}
//阶乘问题
public static int factorial(int n) {
if (n == 1) {
return 1;
} else {
return factorial(n - 1) * n; // 1 * 2 * 3
}
}
}
代码实现
import java.util.Scanner;
public class Test2 {
public static void main(String[] args) {
//先创建一个二维数组,模拟迷宫
// 地图
int[][] map = new int[8][7];
//使用1 表示墙
//上下全部置为1
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//左右全部置为1
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
//设置挡板, 1表示
map[3][1] = 1;
map[3][2] = 1;
//输出地图
System.out.println("地图的情况");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
//使用递归回溯给小球找路
setway(map,1,1);
//输出新的地图,小球走过,并标识过的递归
System.out.println("小球走过,并标识过的地图的情况");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
//使用递归回溯来给小球找路
//说明
//1.map表示地图
//2.i,j表示从地图的哪个位置开始出发(i,j)
//3.如果小球能到map[6][5]位置,则说明通路找到
//4.约定:当map[i][j]为0表示该点没有走过 当为1表示墙:2表示通路可以走;3表示该点已经走过走不通
//5.在走迷宫时,需要确定一个策略(方法)下->右->上->左,如果该点走不通再回溯
/**
*
* @param map 表示地图
* @param i 从哪个位置开始找
* @param j
* @return 如果找到通路,就返回true,否则返回false
*/
public static boolean setway(int[][] map, int i ,int j){
if(map[6][5] == 2){//通路已经找到ok
return true;
}else{
if (map[i][j] == 0){//如果当前这个点还没有走过
//按照策略下->右->上->左
map[i][j] = 2;//假定该点是可以走通
if (setway(map,i+1,j)){//向下走
return true;
} else if (setway(map,i,j+1)){//向右走
return true;
}else if (setway(map,i-1,j)){//向上走
return true;
}else if (setway(map,i,j-1)){//向左走
return true;
} else{
//说明该点走不通,是死路
map[i][j] = 3;
return false;
}
} else {//如果map[i][j] != 0 ; 可能是1,2,3
return false;
}
}
}
}
对迷宫问题的讨论
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、 同一列或同一斜线上,问有多少种摆法**(92)**。
解题思路
说明:
代码实现
import java.util.Queue;
public class Test3 {
//定义一个max表示共有多少个皇后
int max = 8;
//定义数组array,保存皇后放置位置的结果,比如arr = {0,4,7,5,2,6,1,3}
int[] array = new int[max];
static int count = 0;
public static void main(String[] args) {
//测试一下
Test3 queue8 = new Test3();
queue8.check(0);
System.out.printf("一共有%d解法",count);
}
//编写一个方法,放置第n个皇后
//特别注意:check是每一次递归时,进入到check中有for(int i = 0; i < max; i++),因此会有回溯
private void check(int n){
if (n == max){//n = 8,其实8个皇后就已经放好
print();
return;
}
//依次放入皇后,并判断是否冲突
for (int i = 0; i < max; i++){
//先把当前这个皇后n,放到该行的第一列
array[n] = i;
//判断当放置第n个皇后到i列时,是否冲突
if (judge(n)){//不冲突
//接着放n+1个皇后,即开始递归
check(n+1);
}
//如果冲突,就继续执行array[n] = i;即将第n个皇后,放置在本行的后移的一个位置
}
}
//查看当我们放置第n个皇后,就去检测该皇后是否和前面已经摆放的皇后冲突
/**
* @param n
* @return
*/
private boolean judge(int n) {
for (int i = 0; i < n; i++) {
//说明
//1.array[i] == array[n] 表示判断第n个皇后是否和前面的n-1个皇后在同一列
//2.Math.abs(n-1) == Math.abs(array[n] - array[i]) 表示判断第n个皇后是否和第i皇后是否在同一斜线
//3.判断是否在同一行,没有必要,n每次都在递增
if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
return false;
}
}
return true;
}
//写一个方法,可以将皇后摆放的位置输出
public void print() {
count++;
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
}
排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程
时间频度:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为 T(n)。
举例说明-基本案例:
举例说明-忽略常数项:
结论:
举例说明-忽略系数
结论:
说明:
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较 相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒
优化:
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在 排序过程中设置一个标志 flag 判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排 序写好后,在进行)
小结上面的图解过程:
我们举一个具体的案例来说明冒泡法。我们将五个无序的数:3, 9, -1, 10, -2 使用冒泡排序法将其排成一个从小 到大的有序数列
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Scanner;
public class Test4 {
public static void main(String[] args) {
// int arr[] = {3, 9, -1, 10, -2};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
//为了容易理解,我们把冒泡排序的演变过程,给大家展示
//测试一下冒泡排序的速度O(n^2),给80000个数据,测试
//创建要给80000个的随机的数据
int[] arr = new int[80000];
for (int i = 0; i < arr.length;i++){
arr[i] = (int) (Math.random()*80000);
}
Date datal = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(datal);
System.out.println("排序前的时间是="+date1Str);
//测试冒泡排序
bubbleSort(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是="+date2Str);
//System.out.println("排序后");
//System.out.println(Arrays.toString(arr));
/*
//第二趟排序,就是将第二大的数排在倒数第二位
for (int j = 0; j < arr.length - 2; j++) {
//如果前面的数比后面的数打,则交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第二趟排序后的数据");
System.out.println(Arrays.toString(arr));
//第3趟排序,就是将第3大的数排在倒数第3位
for (int j = 0; j < arr.length - 3; j++) {
//如果前面的数比后面的数打,则交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第三趟排序后的数据");
System.out.println(Arrays.toString(arr));
//第4趟排序,就是将第4大的数排在倒数第4位
for (int j = 0; j < arr.length - 4; j++) {
//如果前面的数比后面的数打,则交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第四趟排序后的数据");
System.out.println(Arrays.toString(arr));
*/
}
//将前面冒泡排序算法,封装成一个方法
public static void bubbleSort(int[] arr) {
//冒泡排序的时间复杂度O(n^2)
int temp = 0;//临时变量
boolean flag = false; //标识变量,表示是否进行过交换
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1; j++) {
//如果前面的数比后面的数打,则交换
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
//System.out.println("第" + (i + 1) + "趟排序后的数据");
//System.out.println(Arrays.toString(arr));
if (!flag) {//在一趟排序中,一次交换都没有发生过
break;
} else {
flag = false;//重置flag,进行下次判断
}
}
}
}
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的
选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从 arr[0]~arr[n-1]中选取最小值, 与 arr[0]交换,第二次从 arr[1]~arr[n-1]中选取最小值,与 arr[1]交换,第三次从 arr[2]~arr[n-1]中选取最小值,与 arr[2] 交换,…,第 i 次从 arr[i-1]~arr[n-1]中选取最小值,与 arr[i-1]交换,…, 第 n-1 次从 arr[n-2]~arr[n-1]中选取最小值, 与 arr[n-2]交换,总共通过 n-1 次,得到一个按排序码从小到大排列的有序序列。
分析图:
对一个数组的选择排序再进行讲解:
选择排序应用实例:
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Scanner;
//选择排序
public class Test2 {
public static void main(String[] args) {
//int[] arr = {101, 34, 119, 1};
//创建要给80000个的随机的数据
int[] arr = new int[80000];
for (int i = 0; i < arr.length;i++){
arr[i] = (int) (Math.random()*80000);
}
//System.out.println("排序前");
//System.out.println(Arrays.toString(arr));
Date datal = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(datal);
System.out.println("排序前的时间是="+date1Str);
selectSort(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是="+date2Str);
//System.out.println("排序后");
//System.out.println(Arrays.toString(arr));
}
//选择排序
public static void selectSort(int[] arr) {
//在推导的过程,我们发现了规律,因此,可以使用for来解决
//选择排序的时间复杂度0(n^2)
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
int min = arr[i];
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) {//说明假定的最小值,并不是最小
min = arr[j];//重置min
minIndex = j;//重置minIndex
}
}
//将最小值,放在arr[0],即交换
if (minIndex != 0) {
arr[minIndex] = arr[i];
arr[i] = min;
}
//System.out.println("第"+(i+1)+"轮后~~");
//System.out.println(Arrays.toString(arr));
}
//使用逐步推导的方式来,讲解选择排序
//第一轮
//原始的数组: 101,34,119,1
//第一轮排序: 1,34,119,101
//算法 先简单--》做复杂,就是可以把一个复杂的算法,拆分成简单的问题 --》 逐步解决
//第一轮
/* int minIndex = 0;
int min = arr[0];
for (int j = 0 + 1; j < arr.length; j++) {
if (min > arr[j]) {//说明假定的最小值,并不是最小
min = arr[j];//重置min
minIndex = j;//重置minIndex
}
}
//将最小值,放在arr[0],即交换
if (minIndex != 0) {
arr[minIndex] = arr[0];
arr[0] = min;
}
System.out.println("第一轮后~~");
System.out.println(Arrays.toString(arr));
//第二轮
minIndex = 1;
min = arr[1];
for (int j = 1 + 1; j < arr.length; j++) {
if (min > arr[j]) {//说明假定的最小值,并不是最小
min = arr[j];//重置min
minIndex = j;//重置minIndex
}
}
//将最小值,放在arr[0],即交换
if (minIndex != 1) {
arr[minIndex] = arr[1];
arr[1] = min;
}
System.out.println("第二轮后~~");
System.out.println(Arrays.toString(arr));
//第三轮
minIndex = 2;
min = arr[2];
for (int j = 2 + 1; j < arr.length; j++) {
if (min > arr[j]) {//说明假定的最小值,并不是最小
min = arr[j];//重置min
minIndex = j;//重置minIndex
}
}
//将最小值,放在arr[0],即交换
if (minIndex != 2) {
arr[minIndex] = arr[2];
arr[2] = min;
}
System.out.println("第三轮后~~");
System.out.println(Arrays.toString(arr));
*/
}
}
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
插入排序(Insertion Sorting)的基本思想是:把 n 个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有 n-1 个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
插入排序思路图:
应用实例:
有一群小牛,考试成绩分别是101,34,119,1 请从小到大排序
代码实现:
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class Test1 {
public static void main(String[] args) {
//int[] arr = {101, 34, 119, 1};
//创建要给80000个的随机的数据
int[] arr = new int[80000];
for (int i = 0; i < arr.length;i++){
arr[i] = (int) (Math.random()*80000);
}
Date datal = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(datal);
System.out.println("排序前的时间是="+date1Str);
insertSort(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是="+date2Str);
}
//插入排序
public static void insertSort(int[] arr) {
//使用for循环来代码简化
for (int i = 1;i < arr.length;i++){
int insertVal = arr[i];
int insertIndex = i - 1;//即arr[1]的前面这个数的下标
//给insertVal找到插入的位置
//说明
//1.insertIndex >= 0 保证在给insertVal 找插入位置,不越界
//2.insertVal < arr[insertIndex] 将插入的数,还没有找到插入位置
//3.
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex +1] = arr[insertIndex];
insertIndex--;
}
//当推出while循环时,说明插入的位置找到,insertIndex + 1
// 这里我们判断是否需要赋值
if (insertIndex + 1 != i) {
arr[insertIndex + 1] = insertVal;
}
//System.out.println("第"+i+"轮插入");
//System.out.println(Arrays.toString(arr));
}
//使用逐步推导的方式来讲解,便于理解
//第一轮{101,34,119,1}; => {34,101,119,1}
//定义待插入的数
/* int insertVal = arr[1];
int insertIndex = 1 - 1;//即arr[1]的前面这个数的下标
//给insertVal找到插入的位置
//说明
//1.insertIndex >= 0 保证在给insertVal 找插入位置,不越界
//2.insertVal < arr[insertIndex] 将插入的数,还没有找到插入位置
//3.
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex +1] = arr[insertIndex];
insertIndex--;
}
//当推出while循环时,说明插入的位置找到,insertIndex + 1
arr[insertIndex + 1] = insertVal;
System.out.println("第一轮插入");
System.out.println(Arrays.toString(arr));
//第二轮
insertVal = arr[2];
insertIndex = 2 - 1;
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex +1] = arr[insertIndex];
insertIndex--;
}
arr[insertIndex + 1] = insertVal;
System.out.println("第二轮插入");
System.out.println(Arrays.toString(arr));
//第三轮
insertVal = arr[3];
insertIndex = 3 - 1;
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex +1] = arr[insertIndex];
insertIndex--;
}
arr[insertIndex + 1] = insertVal;
System.out.println("第三轮插入");
System.out.println(Arrays.toString(arr));
*/
}
}
结论: 当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响.
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
示意图
应用实例:
代码实现:
交换法
import java.util.Arrays;
public class Test2 {
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
shellSort(arr);
}
//使用逐步推导的方式来编写希尔排序
public static void shellSort(int[] arr) {
int temp = 0;
int count = 0;
//根据前面的逐步分析,使用循环处理
//希尔排序时,对有序序列在插入时采用交换法
//思路(算法) ===》代码
for (int gap = arr.length/2;gap>0;gap/=2){
for (int i = gap; i < arr.length; i++) {
//遍历各组中所有的元素(共gap组,每组有个元素),步长gap
for (int j = i - gap; j >= 0; j -= gap) {
//如果当前元素大于加上步长后的元素,说明交换
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
System.out.println("希尔排序"+(++count)+"轮后="+ Arrays.toString(arr));
}
//希尔排序的第一轮排序
//因为第一轮排序,是将10个数据分成了5组
/* for (int i = 5; i < arr.length; i++) {
//遍历各组中所有的元素(共五组,每组有两个元素),步长5
for (int j = i - 5; j >= 0; j -= 5) {
//如果当前元素大于加上步长后的元素,说明交换
if (arr[j] > arr[j + 5]) {
temp = arr[j];
arr[j] = arr[j + 5];
arr[j + 5] = temp;
}
}
}
System.out.println("希尔排序1轮后="+ Arrays.toString(arr));
//希尔排序的第二轮排序
//因为第二轮排序,是将10个数据分成了5/2 = 2组
for (int i = 2; i < arr.length; i++) {
//遍历各组中所有的元素(共2组,每组有5个元素),步长2
for (int j = i - 2; j >= 0; j -= 2) {
//如果当前元素大于加上步长后的元素,说明交换
if (arr[j] > arr[j + 2]) {
temp = arr[j];
arr[j] = arr[j + 2];
arr[j + 2] = temp;
}
}
}
System.out.println("希尔排序2轮后="+ Arrays.toString(arr));
//希尔排序的第三轮排序
//因为第三轮排序,是将10个数据分成了2/2 = 1组
for (int i = 1; i < arr.length; i++) {
//遍历各组中所有的元素(共1组,每组有10个元素),步长1
for (int j = i - 1; j >= 0; j -= 1) {
//如果当前元素大于加上步长后的元素,说明交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println("希尔排序3轮后="+ Arrays.toString(arr));
*/
}
}
移动法
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class Test2 {
public static void main(String[] args) {
//int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
//创建要给80000个的随机的数据
int[] arr = new int[8];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 80000);
}
Date datal = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(datal);
System.out.println("排序前的时间是=" + date1Str);
shellSort2(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
}
//使用逐步推导的方式来编写希尔排序
public static void shellSort(int[] arr) {
int temp = 0;
int count = 0;
//根据前面的逐步分析,使用循环处理
//希尔排序时,对有序序列在插入时采用交换法
//思路(算法) ===》代码
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
//遍历各组中所有的元素(共gap组,每组有个元素),步长gap
for (int j = i - gap; j >= 0; j -= gap) {
//如果当前元素大于加上步长后的元素,说明交换
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
//System.out.println("希尔排序"+(++count)+"轮后="+ Arrays.toString(arr));
}
//希尔排序的第一轮排序
//因为第一轮排序,是将10个数据分成了5组
/* for (int i = 5; i < arr.length; i++) {
//遍历各组中所有的元素(共五组,每组有两个元素),步长5
for (int j = i - 5; j >= 0; j -= 5) {
//如果当前元素大于加上步长后的元素,说明交换
if (arr[j] > arr[j + 5]) {
temp = arr[j];
arr[j] = arr[j + 5];
arr[j + 5] = temp;
}
}
}
System.out.println("希尔排序1轮后="+ Arrays.toString(arr));
//希尔排序的第二轮排序
//因为第二轮排序,是将10个数据分成了5/2 = 2组
for (int i = 2; i < arr.length; i++) {
//遍历各组中所有的元素(共2组,每组有5个元素),步长2
for (int j = i - 2; j >= 0; j -= 2) {
//如果当前元素大于加上步长后的元素,说明交换
if (arr[j] > arr[j + 2]) {
temp = arr[j];
arr[j] = arr[j + 2];
arr[j + 2] = temp;
}
}
}
System.out.println("希尔排序2轮后="+ Arrays.toString(arr));
//希尔排序的第三轮排序
//因为第三轮排序,是将10个数据分成了2/2 = 1组
for (int i = 1; i < arr.length; i++) {
//遍历各组中所有的元素(共1组,每组有10个元素),步长1
for (int j = i - 1; j >= 0; j -= 1) {
//如果当前元素大于加上步长后的元素,说明交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println("希尔排序3轮后="+ Arrays.toString(arr));
*/
}
//对交换式的希尔排序进行优化-》位移法
public static void shellSort2(int[] arr) {
//增量gap,并逐步的缩小增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
//从第gap个元素,逐个对其所在组进行直接插入排序
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
//移动
arr[j] = arr[j - gap];
j -= gap;
}
//当退出while后,就给temp找到插入的位置
arr[j] = temp;
}
}
}
}
}
快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
应用实例
要求: 对 [-9,78,0,23,-567,70] 进行从小到大的排序,要求使用快速排序法。【测试8w和800w】
说明[验证分析]:
1.如果取消左右递归,结果是 -9 -567 0 23 78 70
2.如果取消右递归,结果是 -567 -9 0 23 78 70
3.如果取消左递归,结果是 -9 -567 0 23 70 78
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class Test4 {
public static void main(String[] args) {
// int[] arr = {-9, 78, 0, 23, -567, 70};
// quickSort(arr, 0, arr.length - 1);
// System.out.println("arr=" + Arrays.toString(arr));
//测试快排的执行速度
//创建要给80000个的随机的数据
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 80000);
}
Date datal = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(datal);
System.out.println("排序前的时间是=" + date1Str);
quickSort(arr,0,arr.length-1);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
}
public static void quickSort(int[] arr, int left, int right) {
int l = left;//左下标
int r = right;//右下标
//pivot 中轴值
int pivot = arr[(left + right) / 2];
int temp = 0;//临时变量,作为交换时使用
//while循环的目的是让比pivot值小放到左边
//比pivot值大放到右边
while (l < r) {
//在pivot的左边一直找,找到大于等于pivot值,才退出
while (arr[l] < pivot) {
l += 1;
}
//在pivot的右边一直找,找到小于等于pivot值,才退出
while (arr[r] > pivot) {
r -= 1;
}
//如果l >= r 说明pivot的左右两的值,已经按照左边全部是
//小于等于pivot值,右边全部是大于等于pivot值
if (l >= r) {
break;
}
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交换完后,发现这个arr[l] == pivot的值相等 r--,
if (arr[l] == pivot) {
r -= 1;
}
//如果交换完后,发现这个arr[r] == pivot的值相等 l++,
if (arr[r] == pivot) {
l += 1;
}
}
//如果l==r,必须l++,r--,否则会出现栈溢出
if (l == r) {
l += 1;
r -= 1;
}
//向左递归
if (left < r) {
quickSort(arr, left, r);
}
//向右递归
if (right > l) {
quickSort(arr, l, right);
}
}
}
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class Test1 {
public static void main(String[] args) {
// int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
// int temp[] = new int[arr.length];//归并排序需要一个额外空间
// margeSort(arr, 0, arr.length - 1, temp);
// System.out.println("归并排序后=" + Arrays.toString(arr));
//创建要给80000个的随机的数据
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 80000);
}
int temp[] = new int[arr.length];//归并排序需要一个额外空间
Date datal = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(datal);
System.out.println("排序前的时间是=" + date1Str);
margeSort(arr, 0, arr.length - 1, temp);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
}
//分加合的方法
public static void margeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2;//中间索引
//向左递归进行分解
margeSort(arr, left, mid, temp);
//向右递归进行分解
margeSort(arr, mid + 1, right, temp);
//合并
marge(arr, left, mid, right, temp);
}
}
//合并的方法
/**
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void marge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;//初始化i,左边有序序列的初始索引
int j = mid + 1;//初始化j,右边有序序列的初始索引
int t = 0;//指向temp数组的当前索引
//(一)
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i <= mid && j <= right) {//继续
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到temp数组
//然后t++,i++
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
t += 1;
i += 1;
} else {//反之,将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
t += 1;
j += 1;
}
}
//(二)
//把有剩余数据的一边的数据依次全部填充到temp
while (i <= mid) {//左边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[i];
t += 1;
i += 1;
}
while (j <= right) {//右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
t += 1;
j += 1;
}
//(三)
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left;
//第一次合并tempLeft = 0, right = 1 // tempLeft = 2 right = 3
// tl=0,ri=3
//最后一次tempLeft = 0 right = 7
while (tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
}
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class Test2 {
public static void main(String[] args) {
// int arr[] = {53, 3, 542, 748, 14, 214};
// radixSort(arr);
//创建要给80000个的随机的数据
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 80000);
}
int temp[] = new int[arr.length];//归并排序需要一个额外空间
Date datal = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(datal);
System.out.println("排序前的时间是=" + date1Str);
radixSort(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
}
//基数排序方法
public static void radixSort(int[] arr) {
//根据前面的推导过程,我们可以得到最终的基数排序代码
//1.得到数组中最大得到数的位数
int max = arr[0];//假设第一数就是最大值
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大数是几位数
int maxLength = (max + "").length();
//
//第一轮(针对每个元素的个位进行排序处理)
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
//说明
//1.二维数组包含10个一维数组
//2.为了防止在放入数的时候,数量溢出,则每个一维数组(桶),大小定为arr.length
//3.很明确,基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//可以这样理解
//比如:bucketElementCounts[0],记录的就是bucket[0]桶的放入数据个数
int[] bucketElementCounts = new int[10];
//这里我们使用循环将代码处理
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//第(i+1)轮(针对每个元素的对应位进行排序处理)
for (int j = 0; j < arr.length; j++) {
//取出每个元素的对应位的值
int digitOfElement = arr[j] / n % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一遍,并将桶中的数据,放入到原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
//循环该桶即第k个桶(即第k个一维数组)放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到arr
arr[index++] = bucket[k][l];
}
}
//第(i+1)轮处理后,需要将每个bucketElementCounts[k] = 0!!!!
bucketElementCounts[k] = 0;
}
// System.out.println("第"+(i+1)+"轮,对个位的排序处理arr=" + Arrays.toString(arr));
}
/*
//第一轮(针对每个元素的个位进行排序处理)
for (int j = 0; j < arr.length; j++) {
//取出每个元素的个位的值
int digitOfElement = arr[j] / 1 % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一遍,并将桶中的数据,放入到原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
//循环该桶即第k个桶(即第k个一维数组)放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到arr
arr[index++] = bucket[k][l];
}
}
//第一轮处理后,需要将每个bucketElementCounts[k] = 0!!!!
bucketElementCounts[k] = 0;
}
System.out.println("第一轮,对个位的排序处理arr=" + Arrays.toString(arr));
//===============================================
//第二轮(针对每个元素的十位进行排序处理)
for (int j = 0; j < arr.length; j++) {
//取出每个元素的个位的值
int digitOfElement = arr[j] / 10 % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
index = 0;
//遍历每一遍,并将桶中的数据,放入到原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
//循环该桶即第k个桶(即第k个一维数组)放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到arr
arr[index++] = bucket[k][l];
}
}
//第二轮处理后,需要将每个bucketElementCounts[k] = 0!!!!
bucketElementCounts[k] = 0;
}
System.out.println("第二轮,对个位的排序处理arr=" + Arrays.toString(arr));
//第三轮(针对每个元素的百位进行排序处理)
for (int j = 0; j < arr.length; j++) {
//取出每个元素的个位的值
int digitOfElement = arr[j] / 100 % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
index = 0;
//遍历每一遍,并将桶中的数据,放入到原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
//循环该桶即第k个桶(即第k个一维数组)放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到arr
arr[index++] = bucket[k][l];
}
}
}
System.out.println("第三轮,对个位的排序处理arr=" + Arrays.toString(arr));
*/
}
}
在java中,我们常用的查找有四种:
有一个数列: {1,8, 10, 89, 1000, 1234} ,判断数列中是否包含此名称【顺序查找】 要求: 如果找到了,就提示找到,并给出下标值。
代码实现
public class Test2 {
public static void main(String[] args) {
int[] arr = {1, 9, 11, -1, 34, 89};//没有顺序的数组
int index = seqSearch(arr, 11);
if (index == -1) {
System.out.println("没有找到");
} else {
System.out.println("找到,下标为=" + index);
}
}
/**
* 这里我们实现的线性查找是找到一个满足条件的值,就返回
*
* @param arr
* @param value
* @return
*/
public static int seqSearch(int[] arr, int value) {
//线性查找是逐一比对,发现有相同值,就返回下标
for (int i = 0; i < arr.length; i++) {
if (arr[i] == value) {
return i;
}
}
return -1;
}
}
请对一个有序数组进行二分查找 {1,8, 10, 89, 1000, 1234} ,输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。
代码实现
说明:增加了找到所有的满足条件的元素下标:
课后思考题: {1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,有多个相同的数值时,如何将所有的数值都查找到,比如这里的1000
import java.util.ArrayList;
import java.util.List;
public class Test2 {
//注意:使用二分查找的前提是该数组是有序的
public static void main(String[] args) {
int[] arr = {1, 8, 10, 89,1000, 1234};
//int resIndex = binarySearch(arr, 0, arr.length - 1, 88);
// System.out.println("resIndex=" + resIndex);
List<Integer> resIndexList = binarySearch2(arr,0,arr.length-1,1000);
System.out.println("resIndexList=" + resIndexList);
}
//二分查找算法
/**
* @param arr 数组
* @param left 左边的索引
* @param right 右边的索引
* @param findVal 要查找的值
* @return 如果找到就返回下标,如果没有找到,就返回-1
*/
public static int binarySearch(int[] arr, int left, int right, int findVal) {
//当left>right时,说明递归整个数组,但是没有找到
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
return binarySearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
//完成一个课后思考题
//课后思考题: {1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,
// 有多个相同的数值时,如何将所有的数值都查找到,比如这里的1000
// 思路分析
//1.找到mid索引值,不能马上返回
//2.在找到mid索引值的左边扫描,将所有满足1000的元素的下标,加入到集合ArrayList
//3.在找到mid索引值的右边扫描,将所有满足1000的元素的下标,加入到集合ArrayList
//4.将Arraylist返回
public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal) {
//当left>right时,说明递归整个数组,但是没有找到
if (left > right) {
return new ArrayList<Integer>();
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
return binarySearch2(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearch2(arr, left, mid - 1, findVal);
} else {
// 思路分析
//1.找到mid索引值,不能马上返回
//2.在找到mid索引值的左边扫描,将所有满足1000的元素的下标,加入到集合ArrayList
//3.在找到mid索引值的右边扫描,将所有满足1000的元素的下标,加入到集合ArrayList
//4.将Arraylist返回
List<Integer> resIndexlist = new ArrayList<Integer>();
//向mid索引值左边扫描,将所有满足1000的元素的下标,加入到集合ArrayList
int temp = mid -1;
while (true){
if (temp < 0 || arr[temp] != findVal){//退出
break;
}
//否则,就temp放入到resIndexlist
resIndexlist.add(temp);
temp -= 1;
}
resIndexlist.add(mid);
//向mid索引值右边扫描,将所有满足1000的元素的下标,加入到集合ArrayList
temp = mid +1;
while (true){
if (temp > arr.length-1 || arr[temp] != findVal){//退出
break;
}
//否则,就temp放入到resIndexlist
resIndexlist.add(temp);
temp += 1;
}
return resIndexlist;
}
}
}
插值查找原理介绍:
插值查找算法类似于二分查找,不同的是插值查找每次从自适应 mid 处开始查找。
将折半查找中的求 mid 索引的公式 , low 表示左边索引 left, high 表示右边索引 right。key 就是前面我们讲的findVal(查找值)
int mid = low + (high - low) * (key - arr[low]) / (arr[high] - arr[low]) ;/插值索引/
对应前面的代码公式:
int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left])
请对一个有序数组进行插值查找 {1,8, 10, 89, 1000, 1234} ,输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。
代码实现
import org.w3c.dom.html.HTMLIsIndexElement;
import java.util.Arrays;
public class Test4 {
public static void main(String[] args) {
int[] arr = new int[100];
for (int i = 0; i < 100; i++) {
arr[i] = i + 1;
}
int index = insertValueSearch(arr,0,arr.length-1,1);
//int index = binarySearch(arr, 0, arr.length, 100);
System.out.println("index=" + index);
// System.out.println(Arrays.toString(arr));
}
public static int binarySearch(int[] arr, int left, int right, int findVal) {
System.out.println("二分查找被调用~");
//当left>right时,说明递归整个数组,但是没有找到
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
return binarySearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
//编写插值查找算法
/**
* @param arr 数组
* @param left 左边索引
* @param right 右边的索引
* @param findVal 查找值
* @return 如果找到,就返回对应下标,如果没有找到,返回-1
*/
public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
System.out.println("插值查找次数~~");
//注意findVal < arr[0]和 findVal > arr[arr.length -1]必须需要
//否则我们得到的mid可能越界
if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
return -1;
}
//求出mid,自适应
int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
int midVal = arr[mid];
if (findVal > midVal) {//说明应该向右边递归
return insertValueSearch(arr, mid + 1, right, findVal);
} else if (findVal > midVal) {//说明应该向左边递归
return insertValueSearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
}
黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是 0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神奇的数字,会带来意向不大的效果。
斐波那契数列 {1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } 发现斐波那契数列的两个相邻数 的比例,无限接近 黄金分割值
0.618
斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid 不再是中间或插值得到,而是位于黄金分割点附近,即 mid=low+F(k-1)-1(F 代表斐波那契数列),如下图所示
请对一个有序数组进行斐波那契查找 {1,8, 10, 89, 1000, 1234} ,输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。
代码实现
import java.util.Arrays;
public class Test1 {
public static int maxSize = 20;
public static void main(String[] args) {
int[] arr = {1, 8, 10, 89, 1000, 1234};
System.out.println("index="+fibSearch(arr,1234));
}
//因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列
//非递归方法得到一个斐波那契数列
public static int[] fib() {
int[] f = new int[maxSize];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < maxSize; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f;
}
//编写斐波那契查找算法
//使用非递归的方式编写算法
/**
* @param a 数组
* @param key 我们需要查找的关键值
* @return 返回对应的下标,如果没有返回-1
*/
public static int fibSearch(int[] a, int key) {
int low = 0;
int high = a.length - 1;
int k = 0;//表示斐波那契分割数组的下标
int mid = 0;//存放mid值
int f[] = fib();//获取到斐波那契数列
//获取到斐波那契分割数值的下标
while (high > f[k] - 1) {
k++;
}
//因为f[k]值可能大于a的长度,因此我们需要使用Arrays类,构造一个新的数组,并指向temp[]
//不是的部分会使用0填充
int[] temp = Arrays.copyOf(a, f[k]);
//实际上需求使用a数组最后的数填充temp
//举例:
//temp = {1,8,10,89,1000,1234,0,0,0} =>temp = {1,8,10,89,1000,1234,1234,1234,1234}
for (int i = high + 1; i < temp.length; i++) {
temp[i] = a[high];
}
//使用while来循环处理,找到我们的数key
while (low <= high) {//只要个条件满足,就可以找到
mid = low + f[k - 1] - 1;
if (key < temp[mid]){//我们应该向继续向数组的前面查找
high = mid -1;
//为什么是k--
//说明
//1.全部元素=循环的元素+后边的元素
//2.f[k]=f[k-1]+f[k-2]
//因为前面有f[k-1]个元素,所以可以继续拆分f[k-1]=f[k-2]+f[k-3]
//即在f[k-1]前面继续查找k--
//即下次循环mid = f[k-1-1]-1
k--;
}else if (key > temp[mid]){//我们应该向继续向数组的后面查找
low = mid + 1;
//为什么是k--
//说明
//1.全部元素=循环的元素+后边的元素
//2.f[k]=f[k-1]+f[k-2]
//因为前面有f[k-1]个元素,所以可以继续拆分f[k-2]=f[k-3]+f[k-4]
//即在f[k-2]前面继续查找k-=2
//即下次循环mid = f[k-1-2]-1
k -=2;
} else {//找到
//需要确定,返回的是哪个下标
if (mid <= high){
return mid;
} else {
return high;
}
}
}
return -1;//没有找到
}
}
哈希表(散列)-Google上机题
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址…),当输入该员工的 id 时, 要求查找到该员工的 所有信息.
要求:
代码实现
import javax.crypto.EncryptedPrivateKeyInfo;
import java.awt.*;
import java.util.Scanner;
import java.util.SplittableRandom;
public class Test2 {
public static void main(String[] args) {
// 创建哈希表
HashTab hashTab = new HashTab(7);
//写一个简单的菜单
String key = "";
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("add: 添加雇员");
System.out.println("list: 显示雇员");
System.out.println("find: 查找雇员");
System.out.println("exit: 退出系统");
key = scanner.next();
switch (key) {
case "add":
System.out.println("输入id");
int id = scanner.nextInt();
System.out.println("输入名字");
String name = scanner.next();
//创建雇员
Emp emp = new Emp(id, name);
hashTab.add(emp);
break;
case "list":
hashTab.list();
break;
case "find":
System.out.println("请输入要查找的id");
id = scanner.nextInt();
hashTab.findEmpById(id);
break;
case "exit":
scanner.close();
System.exit(0);
default:
break;
}
}
}
}
//创建HashTab 管理多条链表
class HashTab {
private EmpLinkedList[] empLinkedListArray;
private int size;//表示共有多少条链表
//构造器
public HashTab(int size) {
this.size = size;
//初始化empLinkedLists
empLinkedListArray = new EmpLinkedList[size];
//这是不要忘记分别初始化每个链表
for (int i = 0; i < size; i++) {
empLinkedListArray[i] = new EmpLinkedList();
}
}
//添加雇员
public void add(Emp emp) {
//根据员工的id,得到该员工应当添加到哪条链表
int empLinkedListNO = hashFun(emp.id);
//将emp添加到对应的链表中
empLinkedListArray[empLinkedListNO].add(emp);
}
//遍历所有的链表
public void list() {
for (int i = 0; i < size; i++) {
empLinkedListArray[i].list(i);
}
}
//根据输入的id,查找雇员
public void findEmpById(int id){
//使用散列函数确定到哪条链表查找
int empLinkedListNO = hashFun(id);
Emp emp = empLinkedListArray[empLinkedListNO].findEmpById(id);
if (emp != null){//找到
System.out.printf("在第%d条链表中找到雇员 id=%d\n",(empLinkedListNO+1),id);
} else {
System.out.println("在哈希表中,没有找到该雇员~");
}
}
//编写散列函数,使用一个简单取模法
public int hashFun(int id) {
return id % size;
}
}
//表示一个雇员
class Emp {
public int id;
public String name;
public Emp next;//next默认为空
public Emp(int id, String name) {
super();
this.id = id;
this.name = name;
}
}
//创建EmpLinkedList,表示链表
class EmpLinkedList {
//头指针,执行第一个Emp,因此我们这个链表的head是直接指向第一个Emp
private Emp head;//默认null
//添加雇员链表
//说明
//1.假定,当添加雇员时,id是自增长,即id的分配总是从小到大
//因此我们将该雇员直接加入到本链表的最后即可
public void add(Emp emp) {
//如果是添加第一个雇员
if (head == null) {
head = emp;
return;
}
//如果不是第一个雇员,则使用一个辅助的指针,帮助定位到最后
Emp curEmp = head;
while (true) {
if (curEmp.next == null) {//说明到链表最后
break;
}
curEmp = curEmp.next;//后移
}
//退出是直接将emp加入链表
curEmp.next = emp;
}
//遍历链表的雇员信息
public void list(int no) {
if (head == null) {//说明链表为空
System.out.println("第 " + (no + 1) + " 链表为空");
return;
}
System.out.print("第 " + (no + 1) + " 链表的信息为");
Emp curEmp = head;//辅助指针
while (true) {
System.out.printf(" => id=%d name=%s\t", curEmp.id, curEmp.name);
if (curEmp.next == null) {//说明curEmp已经是最后结点
break;
}
curEmp = curEmp.next;//后移,遍历
}
System.out.println();
}
//根据id查找雇员
//如果查找到,就返回Emp,如果没有找到,就返回null
public Emp findEmpById(int id){
//判断链表是否为空
if (head == null){
System.out.println("链表为空");
return null;
}
//辅助指针
Emp curEmp = head;
while (true){
if (curEmp.id == id){//找到
break;//这时curEmp就指向要查找雇员
}
//退出
if (curEmp.next == null){//说明遍历当前链表没有找到该雇员
curEmp = null;
break;
}
curEmp = curEmp.next;//后移
}
return curEmp;
}
}
数组存储方式的分析
优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
链式存储方式的分析
优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可, 删除效率也很好)。
缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历) [示意图]
树存储方式分析
能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。【示意图,后面详讲】
案例: [7, 3, 10, 1, 5, 9, 12]
树的常用术语: