说明:
线性结构:
1)线性结构:数据元素一对一的线性关系
2)两种存储结构:顺序存储(顺序表)和链式存储(链表)
3)常见:数组、队列、链表和栈
非线性结构:
包括二维数组,多维数组,广义表,树结构,图结构…
简单图解:
二维数组 转 稀疏数组的思路:
sparseArr int [sum + 1 ] [3]
稀疏数组 还原 二维数组
chessArr2 = int [11] [11]
上代码:
package com.wts.sparsearrary;
public class SparseArrary {
public static void main(String[] args) {
//创建一个原始的二维数组 11*11
//0:表示没有棋子, 1表示黑子, 2表示篮子
int chessArr[][] = new int[11][11];
chessArr[1][2] = 1;
chessArr[2][3] = 2;
//输出原始的二维数组
System.out.println("二维数组为-----");
for (int[] row : chessArr) {
for (int data : row) {
System.out.print(data + "\t");
}
System.out.println();
}
//二维数组 ---> 稀疏数组
int sum = 0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (chessArr[i][j] != 0) {
sum++;
}
}
}
//创建稀疏数组
int sparseArr[][] = new int[sum + 1][3];
sparseArr[0][0] = 11;
sparseArr[0][1] = 11;
sparseArr[0][2] = sum;
//将非0数字存入稀疏数组
int count = 0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (chessArr[i][j] != 0) {
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = chessArr[i][j];
}
}
}
//输出稀疏数组
System.out.println("二维数组->稀疏数组如下------");
for (int i = 0; i < sparseArr.length; i++) {
for (int j = 0; j < 3; j++) {
System.out.print(sparseArr[i][j] + "\t");
}
System.out.println();
}
/*---稀疏数组 恢复成 二维数组---*/
/*
* 1、
* 2、
* */
int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
for (int i = 1; i < sparseArr.length; i++) {
chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
//输出二维数组
System.out.println("稀疏数组->二维数组------");
for (int[] row : chessArr2) {
for (int data : row) {
System.out.print(data + "\t");
}
System.out.println();
}
}
}
效果如下:
1.简述:
图解:
rear+1
,当front == rear
为空。rear
小于队列的最大下标maxSize - 1
,将数据存入rear所指的数组元素中,否则不存入;rear == maxSize - 1 //队列满
上代码:(数组简单实现队列)
package com.wts.sparsearrary;
import java.util.Scanner;
public class ArrayQueueDemo {
public static void main(String[] args) {
ArrayQueue arrayQueue = new ArrayQueue(3);
boolean flag = true;
char key = ' ';
Scanner sc = new Scanner(System.in);
while (flag) {
System.out.println("------菜单提示------");
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 = sc.next().charAt(0);
//这里为什么只能用sc.next()呢??
/*
* sc.next()和sc.nextLine()有什么区别呢
这里用nextLine()就会报错哎
*/
switch (key) {
case 's': {
arrayQueue.showQueue();
break;
}
case 'e': {
flag = false;
break;
}
case 'a': {
System.out.print("--输入要添加的数据:");
int n = sc.nextInt();
arrayQueue.addQueue(n);
break;
}
case 'g': {
try {
int result = arrayQueue.getQueue();
System.out.println("取出的数据为:" + result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
}
case 'h': {
try {
arrayQueue.headQueue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
}
}
}
}
}
//使用数组模拟队列 --编写ArrayQueue类
class ArrayQueue {
private int maxSize; // 数组的最大容量,队列的容量
private int front; // 队头
private int rear; // 队尾
private int[] arr; // 数组,用于存放数据,模拟队列
//队列构造器-数组
public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
this.front = -1;
this.rear = -1;
arr = new int[maxSize];
}
//判断队列是否满
public boolean isFull() {
if (rear == maxSize - 1) {
return true;
}
return false;
}
//判断队列是否为空
public boolean isEmpty() {
return rear == front;
}
//添加数据 入队列
public void addQueue(int n) {
//判断满?
if (isFull()) {
System.out.println("队列已满!无法添加!");
return;
}
rear++;
arr[rear] = n;
}
//获取队列的数据,出队列
public int getQueue() {
//判断是否空?
if (isEmpty()) {
throw new RuntimeException("队列空!请先添加数据!");
}
front++;
return arr[front];
}
//遍历
public void showQueue() {
if (isEmpty()) {
throw new RuntimeException("队列空!请先添加数据!");
}
for (int data : arr) {
System.out.println(data);
}
}
//显示队头
public void headQueue() {
if (isEmpty()) {
throw new RuntimeException("队列空!请先添加数据!");
}
System.out.println(arr[front + 1]);
}
}
小结:
环形队列优化!
环形
环形数组优化,(取模的方式实现),环形队列
简述与图解:
arr[front]表示队头数据
(rear+1) % maxsize == front 【满】
rear == front
maxsize
,实际上最大能存储的值是maxsize-1
(((参考博主文章:数组实现环形队列(图解))))
上代码:
package com.wts.sparsearrary.CircleQueue;
import java.util.Scanner;
public class CircleArrayQueueDemo {
public static void main(String[] args) {
CircleArrayQueue circleArrayQueue = new CircleArrayQueue(4);//说明是 4,实际有效最大长度为3
boolean flag = true;
char key = ' ';
Scanner sc = new Scanner(System.in);
while (flag) {
System.out.println("------菜单提示------");
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 = sc.next().charAt(0);
//这里为什么只能用sc.next()呢??
/*
* sc.next()和sc.nextLine()有什么区别呢
这里用nextLine()就会报错哎
*/
switch (key) {
case 's': {
try {
circleArrayQueue.showCircleQueue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
}
case 'e': {
flag = false;
break;
}
case 'a': {
try {
System.out.print("--输入要添加的数据:");
int n = sc.nextInt();
circleArrayQueue.addCircleQueue(n);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
}
case 'g': {
try {
int result = circleArrayQueue.getCircleQueue();
System.out.println("取出的数据为:" + result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
}
case 'h': {
try {
circleArrayQueue.headOfCircleQueue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
}
}
}
}
}
class CircleArrayQueue {
private int front;//指向队头数据
private int rear;// 指向队尾数据的后一位
private int maxsize;
private int[] arr;
CircleArrayQueue(int n) {
this.maxsize = n;
front = 0;
rear = 0;
arr = new int[maxsize];
}
public boolean isEmpty() {
if (rear == front) {
return true;
}
return false;
}
public boolean isFull() {
if ((rear + 1) % maxsize == front) {
return true;
}
return false;
}
//add data into circlequeue
public void addCircleQueue(int n) {
if (isFull()) {
throw new RuntimeException("队列已满!");
}
arr[rear] = n;
rear = (rear + 1) % maxsize;
}
//get data from cq
public int getCircleQueue() {
if (isEmpty()) {
throw new RuntimeException("队列为空!");
}
int value = arr[front];
front = (front + 1) % maxsize;
return value;
}
//size of cq
public int Size() {
return (rear - front + maxsize) % maxsize;
}
//head of cq
public void headOfCircleQueue() {
System.out.println("队头数据为:" + arr[front]);
}
//iterate through cq
public void showCircleQueue() {
if (isEmpty()) {
throw new RuntimeException("队列为空!");
}
//这个遍历没想到 ,还挺细节!
for (int i = front; i < front + Size(); i++) {
System.out.println(arr[i % maxsize]);
}
}
}
小结上图:
总共三部分实现单链表:Demo类、SingleList类(管理单链表)、节点类
上代码:
1 ) 结点类:主要是data部分(这里的data可以包含很多信息)和next部分
/*结点*/
class ListNode {
public int no;
public String data;
public ListNode next;
public ListNode(int no, String data) {
this.no = no;
this.data = data;
}
@Override
public String toString() {
return "ListNode{" +
"no=" + no +
", data='" + data + '\'' +
'}';
}
}
2 ) 单链表管理:包含头结点的初始化、一些单链表的增删改查相关方法等
/*单链表管理*/
class SingleList {
//初始化头节点,头节点不放任何数据
ListNode head = new ListNode(0, "");
public void addList(){...}
public void showList(){...}
......
}
3 )Demo:main函数
public class LinkTestDemo {
public static void main(String[] args) {
//初始化一些结点
ListNode node1 = new ListNode(01, "01");
ListNode node2 = new ListNode(02, "02");
ListNode node3 = new ListNode(03, "03");
SingleList singleList = new SingleList();
/*尾添加 测试*/
singleList.addNode(node1);
singleList.addNode(node2);
singleList.addNode(node3);
/*遍历 */
singleList.showList();
}
}
1)尾添加:
上代码:
//增加数据,尾添加
public void addNode(ListNode node) {
ListNode temp = head;
//temp 指向 尾结点
while (true) {//遍历到尾结点
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = node;
}
上代码:
//插入节点--按照编号no加入结点
//思路:
// 1.找到要插入位置的前一个结点,temp指向它;
//2.如何找到呢?遍历!--temp.next.no > node. no
public void addByOrder(ListNode node) {
ListNode temp = head;//插入结点的用到的temp有讲究的:temp指向插入位置的前一个结点
//遍历-定位到对应的编号no的位置
while (true) {
if (temp.next == null) {//遍历到末尾了
break;
}
if (temp.next.no == node.no) {
//遇到编号no相同的,不能添加
System.out.println("已经存在编号" + node.no + "不能添加!");
return;
}
if (temp.next.no > node.no) {//找到了位置了!
break;
}
temp = temp.next;
}
node.next = temp.next;
temp.next = node;
}
}
上代码:
//遍历链表:
public void showList() {
//判断表格空 可以直接写在这里,不用循环的
if (head == null) {
System.out.println("链表为空!");
return;
}
ListNode temp = head.next;//头结点没有数据,这里表示第一个有效结点
while (true) {
if (temp == null) {
return;
}
System.out.println(temp);
temp = temp.next;
}
}
// ---编程问题总结
// --- 1.指针temp创建的时候,我把它写在了while循环里面,导致每次都读取第一个结点的数据,无限死循环。
// --- 2.判断是否到了最后一个节点的 时候,我写的temp.next==null,导致最后一个结点输出不来。
根据编号no的值,修改结点数据
思路:temp.no == newnode.no
定位
上代码:
//修改结点信息-- 根据no更改结点的数据
//1.temp.no == newnode.no
public void updateNode(ListNode newnode) {
if (head.next == null) {
System.out.println("链表为空");
return;
}
ListNode temp = head;
boolean flag = false;
//定位
while (true) {
if (temp.next == null) {
break;
}
if (temp.no == newnode.no) {
flag = true;//定位成功
break;
}
temp = temp.next;
}
if (flag){
temp.data = newnode.data;
}else {
System.out.println("未能找到匹配的结点");
}
}
思路:temp到底是指向前一个位置还是指向当前需要操作的位置,自己好好理解、甄别。
上代码:
public void deleteNode(ListNode node) {
if (head.next == null) {
System.out.println("链表为空!");
return;
}
ListNode temp = head;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.next.no == node.no) {
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.next = temp.next.next;
}else {
System.out.println("未能找到编号" + node.no + "的数据");
}
}
效果:
思路:遍历,计数
上代码:
//获取单链表的长度--不统计头结点
public int getLength() {
int count = 0;
if (head.next != null) {
//如果链表非空
ListNode temp = head.next;
while (temp != null) {
count++;
temp = temp.next;
}
}
return count;
}
思路:从第一个有效结点开始遍历,倒数第index个,遍历length - index
次
例如,长度4,倒数第k=2
个,从第一个有效节点开始往后遍历4-2=2
次 。
上代码:
//获取倒数第k个结点--
//思路:index为倒数第几个,从第一个有效结点开始,往后遍历(length - index)次
public ListNode getLastIndexNode(int index) {
int length = getLength();
if (length == 0) {
return null;
}
if (index <= 0 && index > length) {
return null;
}
ListNode temp = head.next;
for (int i = 0; i < length - index; i++) {
temp = temp.next;
}
return temp;
}
思路:
head
,表示只认head为头的结点;也就是说后续方法里面新创建的任何新的头结点(比如rehead
),进行相关的更改链表操作后,最后还是要基于head
为头的结点 即,重新保证头结点是head
rehead
,用头插法,把temp和新的rehead头结点相连head
,即,让head
指向新链表的第一个有效节点上代码:
public void reserveList() {
//链表为空,或只有1个结点
if (head.next == null && head.next.next == null) {
System.out.println("链表长度小于2,无法反转!");
return;
}
//新的头结点,-- 我的个人理解是:这是开启了2号链表方便操作,但是最后把头结点换掉!
ListNode rehead = new ListNode(0, "");
ListNode temp = head.next;//从第一个有效结点开始
while (temp != null) {
ListNode tempnext = temp.next;//--很重要!
temp.next = rehead.next;
rehead.next = temp;
temp = tempnext; //--编程问题总结:这里要好好理解tempnext的作用!我总是写成"temp = temp.next",报错,因为上面已经把temp.next改变了,(也就是说改变了temp后面的连接的了)
//--需要一个tempnext在次之前记录 源列表temp的后一个结点的信息!
}
//最后一定要换头结点
head.next = rehead.next;
}
思路:栈,先进后出
上代码:
//**反转打印--不改变原来列表的顺序--使用栈
public void reversePrint() {
if (head.next == null) {
System.out.println("List is empty!");
return;
}
//创建栈
Stack<ListNode> stack = new Stack<>();
ListNode temp = head.next;
while (temp != null) {
//入栈
stack.push(temp);
temp = temp.next;
}
//出栈
while (stack.size() > 0) {
ListNode pop = stack.pop();
System.out.println(pop);
}
}
思路:遍历第一个链表,插入到第二个链表中
上代码:
//合并两个有序数列
public void combineLists(SingleList singleList1, SingleList singleList2) {
if (singleList1.head.next == null || singleList2.head.next == null) {
return;
}
ListNode temp1 = singleList1.head.next;//temp1当前结点
ListNode temp1next = null;
while (temp1 != null) {
temp1next = temp1.next;//这里又用到tempnext了,为什么用?什么时候用??
//我的理解:当问题涉及到两个链表的时候,因为temp1结点可能需要插入到一个新的位置,导致他在原来结点位置的下一个结点和新位置的结点是不一致的,
// 所以需要提前记录temp1原来的下一个结点temp1next!!
ListNode temp2 = singleList2.head;// temp2 要插入位置的前一个结点
while (temp2 != null) {
boolean flag = false;//是否在List2添加成功,
if (temp1.no == temp2.next.no) {//结点重复
break;
}
if (temp1.no < temp2.next.no) {
flag = true;
}
if (flag) {
temp1.next = temp2.next;
temp2.next = temp1;
break;
}else {
temp2 = temp2.next;
}
}
temp1 = temp1next;
}
}
总结:什么时候记录tempnext?
答: 涉及到两个链表的时候,当temp结点插入到新的链表的时候,同时又要满足temp 的下一节点也要进行相关操作
package com.wts._04LinkedList;
import java.util.Stack;
public class LinkTestDemo {
public static void main(String[] args) {
//初始化一些结点
ListNode node1 = new ListNode(01, "01");
ListNode node2 = new ListNode(02, "02");
ListNode node4 = new ListNode(04, "04");
ListNode node3 = new ListNode(03, "03");
SingleList singleList = new SingleList();
/*尾添加 测试*/
// singleList.addNode(node1);
// singleList.addNode(node2);
// singleList.addNode(node4);
// singleList.addNode(node3);
/*遍历 */
//singleList.showList();
//按照编号no插入添加
singleList.addByOrder(node4);
singleList.addByOrder(node1);
singleList.addByOrder(node3);
singleList.addByOrder(node2);
singleList.showList();
//修改结点信息
ListNode node_1 = new ListNode(2, "0002");
singleList.updateNode(node_1);
System.out.println("----修改后的结点为:");
singleList.showList();
/* //删除
singleList.deleteNode(node2);
System.out.println("删除后的链表--");
singleList.showList();*/
//节点个数--链表长度
int length = singleList.getLength();
System.out.println("链表结点个数为:" + length);
System.out.println("-------");
//获取倒数第k个结点
System.out.println("倒数第2个结点为--");
ListNode lastIndexNode = singleList.getLastIndexNode(2);
System.out.println(lastIndexNode);
//--链表反转
System.out.println("--未反转前:");
singleList.showList();
System.out.println("--反转后:");
singleList.reserveList();
singleList.showList();
//--链表反转输出--栈
System.out.println("反链表-反转打印--");
singleList.reversePrint();
System.out.println("--不改变链表");
singleList.showList();
//--两个有序合并成一个有序列表
SingleList singleList1 = new SingleList();
SingleList singleList2 = new SingleList();
ListNode node01 = new ListNode(01, "001");
ListNode node03 = new ListNode(03, "003");
ListNode node05 = new ListNode(05, "005");
ListNode node06_ = new ListNode(06,"006");
ListNode node02 = new ListNode(02, "002");
ListNode node04 = new ListNode(04, "004");
ListNode node06 = new ListNode(06, "006");
singleList1.addNode(node01);
singleList1.addNode(node03);
singleList1.addNode(node05);
singleList1.addNode(node06_);
singleList2.addByOrder(node06);
singleList2.addByOrder(node04);
singleList2.addByOrder(node02);
System.out.println();
System.out.println("链表1--");
singleList1.showList();
System.out.println("链表2--");
singleList2.showList();
//合并
System.out.println("--合并后");
singleList2.combineLists(singleList1, singleList2);
singleList2.showList();
}
}
/*单链表管理*/
class SingleList {
//初始化头节点,头节点不放任何数据
ListNode head = new ListNode(0, "");
//增加数据,尾添加
public void addNode(ListNode node) {
ListNode temp = head;
//temp 指向 尾结点
while (true) {//遍历到尾结点
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = node;
}
//遍历链表:
// ---编程问题总结
// --- 1.指针temp创建的时候,我把它写在了while循环里面,导致每次都读取第一个结点的数据,无限死循环。
// --- 2.判断是否到了最后一个节点的 时候,我写的temp.next==null,导致最后一个结点输出不来。
public void showList() {
//判断表格空 可以直接写在这里,不用循环的
if (head == null) {
System.out.println("链表为空!");
return;
}
ListNode temp = head.next;//头结点没有数据,这里表示第一个有效结点
while (true) {
if (temp == null) {
return;
}
System.out.println(temp);
temp = temp.next;
}
}
//插入节点--按照编号no加入结点
//思路:
// 1.找到要插入位置的前一个结点,temp指向它;
//2.如何找到呢?遍历!--temp.next.no > node. no
public void addByOrder(ListNode node) {
ListNode temp = head;//插入结点的用到的temp有讲究的:temp指向插入位置的前一个结点
//遍历-定位到对应的编号no的位置
while (true) {
if (temp.next == null) {//遍历到末尾了
break;
}
if (temp.next.no == node.no) {
//遇到编号no相同的,不能添加
System.out.println("已经存在编号" + node.no + "不能添加!");
return;
}
if (temp.next.no > node.no) {//找到了位置了!
break;
}
temp = temp.next;
}
node.next = temp.next;
temp.next = node;
}
//修改结点信息-- 根据no更改结点的数据
//1.temp.no == newnode.no
public void updateNode(ListNode newnode) {
if (head.next == null) {
System.out.println("链表为空");
return;
}
ListNode temp = head;
boolean flag = false;
//定位
while (true) {
if (temp.next == null) {
break;
}
if (temp.no == newnode.no) {
flag = true;//定位成功
break;
}
temp = temp.next;
}
if (flag) {
temp.data = newnode.data;
} else {
System.out.println("未能找到匹配的结点");
}
}
public void deleteNode(ListNode node) {
if (head.next == null) {
System.out.println("链表为空!");
return;
}
ListNode temp = head;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.next.no == node.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
} else {
System.out.println("未能找到编号" + node.no + "的数据");
}
}
//获取单链表的长度--不统计头结点
public int getLength() {
int count = 0;
if (head.next != null) {
//如果链表非空
ListNode temp = head.next;
while (temp != null) {
count++;
temp = temp.next;
}
}
return count;
}
//获取倒数第k个结点--
//思路:index为倒数第几个,从第一个有效结点开始,往后遍历(length - index)次
public ListNode getLastIndexNode(int index) {
int length = getLength();
if (length == 0) {
return null;
}
if (index <= 0 && index > length) {
return null;
}
ListNode temp = head.next;
for (int i = 0; i < length - index; i++) {
temp = temp.next;
}
return temp;
}
//反转链表
//思路:!!首先要明白一个点,SingleList这个类,定义了一个head头结点,这就是表示这是一个以head为头的单链表了,
// !!后续在这个类的方法中在创建的任何新的头结点(这里统称为rehead,),对链表做出调整修改后,仍然是基于head为头的结点,
//!!即,需要重新连接,以head为头结点
//1.temp从第一个有效结点开始遍历,同时统计他的下一个几点tempnext
//2.让temp连接到新的头结点rehead(2号链表),tempnext用头插法加入新的链表
//3. 最后让head指向新链表的第一个有效结点
public void reserveList() {
//链表为空,或只有1个结点
if (head.next == null && head.next.next == null) {
System.out.println("链表长度小于2,无法反转!");
return;
}
//新的头结点,-- 我的个人理解是:这是开启了2号链表方便操作,但是最后把头结点换掉!
ListNode rehead = new ListNode(0, "");
ListNode temp = head.next;//从第一个有效结点开始
while (temp != null) {
ListNode tempnext = temp.next;//--很重要!
temp.next = rehead.next;
rehead.next = temp;
temp = tempnext; //--编程问题总结:这里要好好理解tempnext的作用!我总是写成"temp = temp.next",报错,因为上面已经把temp.next改变了,(也就是说改变了temp后面的连接的了)
//--需要一个tempnext在次之前记录 源列表temp的后一个结点的信息!
}
//最后一定要换头结点
head.next = rehead.next;
}
//**反转打印--不改变原来列表的顺序--使用栈
public void reversePrint() {
if (head.next == null) {
System.out.println("List is empty!");
return;
}
//创建栈
Stack<ListNode> stack = new Stack<>();
ListNode temp = head.next;
while (temp != null) {
//入栈
stack.push(temp);
temp = temp.next;
}
//出栈
while (stack.size() > 0) {
ListNode pop = stack.pop();
System.out.println(pop);
}
}
//合并两个有序数列
public void combineLists(SingleList singleList1, SingleList singleList2) {
if (singleList1.head.next == null || singleList2.head.next == null) {
return;
}
ListNode temp1 = singleList1.head.next;//temp1当前结点
ListNode temp1next = null;
while (temp1 != null) {
temp1next = temp1.next;//这里又用到tempnext了,为什么用?什么时候用??
//我的理解:当问题涉及到两个链表的时候,因为temp1结点可能需要插入到一个新的位置,导致他在原来结点位置的下一个结点和新位置的结点是不一致的,
// 所以需要提前记录temp1原来的下一个结点temp1next!!
ListNode temp2 = singleList2.head;// temp2 要插入位置的前一个结点
while (temp2 != null) {
boolean flag = false;//是否在List2添加成功,
if (temp1.no == temp2.next.no) {//结点重复
break;
}
if (temp1.no < temp2.next.no) {
flag = true;
}
if (flag) {
temp1.next = temp2.next;
temp2.next = temp1;
break;
}else {
temp2 = temp2.next;
}
}
temp1 = temp1next;
}
}
}
/*结点*/
class ListNode {
public int no;
public String data;
public ListNode next;
public ListNode(int no, String data) {
this.no = no;
this.data = data;
}
@Override
public String toString() {
return "ListNode{" +
"no=" + no +
", data='" + data + '\'' +
'}';
}
}
单向链表缺点分析:
1)单向链表查找只能一个方向;双向链表还可以从后往前查找
2)单向链表不能自我删除(temp的前一个结点);双向链表可以自我删除
3)相比较单链表多了前驱pre
双链表主要可以自我操作,而单链表依赖前一个结点。
图解:
1)尾插法(尾部添加)
思路:尾部添加,next
和pre
连接即可。
//尾插法
public void addNodeToTail(ListNode2 node) {
ListNode2 temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = node;
node.pre = temp;
}
–有手就行
2)有序插入–根据编号no
需要思考:具体问题代码注释
上代码:
//有序添加--根据no
/*编程问题:
1.第一个结点插入的时候比较特殊
2.核心代码编写的顺序问题也要重视,先连node结点和前一个结点的关系,然后写node和后一个结点的关系
我不知道上述是不是通法,但是要理解
* */
public void addByOrder(ListNode2 node) {
ListNode2 temp = head;
if (head.next == null) {//结点为空,特殊加入
temp.next = node;
node.pre = temp;
return;
}
boolean flag = false;
while (true) {
if (temp.no == node.no) {
System.out.println("结点已经存在!");
break;
}
if (node.no < temp.no) {//找到位置
flag = true;
break;
}
if (temp.next == null) {
break;
}
temp = temp.next;
}
//插入
temp.pre.next = node;
node.pre = temp.pre;
node.next = temp;/*先后顺序?*/
temp.pre = node;
// 错误代码展示:
// node.next = temp;
// temp.pre = node;**
// temp.pre.next = node;**(错误点temp.pre已经指向了别的地方了,)
// node.pre = temp.pre;
}
思考:双向链表插入代码书写的顺序有影响的!具体思考和注意在实践中debug。
根据no删除结点(自我删除)
上代码:
//删除结点--根据结点no的信息
/*遇到问题:删除结点的两行核心代码,我一开始没考虑最后个结点,(最后一个结点的temp.next == null, temp.next.pre就会报错)
* 解决方法:最后一个结点加以判断*/
public void deleteNode(ListNode2 node) {
if (head.next == null) {
System.out.println("空--");
return;
}
ListNode2 temp = head.next;
boolean flag = false; //默认没找到对应的结点
while (temp != null) {
if (temp.no == node.no) {
flag = true;
break;
}
temp = temp.next;
}
if (true) {//删除节点
temp.pre.next = temp.next;
if (temp.next != null) {// *不是最后一个结点才可以执行下面一句话
temp.next.pre = temp.pre;
} else {
System.out.println("没找对编号为" + node.no + "的结点");
return;
}
}
}
注意:考虑最后一个结点
根据编号no–改信息**(自我修改!)**
上代码:
//修改结点
public void updateNode(ListNode2 node) {
//判表空
if (head.next == null) {
System.out.println("双向链表为空---");
return;
}
//找到需要修改的结点,根据编号no
ListNode2 temp = head.next;
boolean flag = false;
while (temp != null) {
if (temp.no == node.no) {//找到了相同的编号no
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
//找到了匹配的编号no,修改信息
temp.id = node.id;
temp.name = node.name;
} else {
System.out.println("未能找到编号为" + node.no + "的数据");
}
}
遍历方式和单链表一样
上代码:
//遍历显示--和单链表一样
public void showList() {
if (head.next == null) {
System.out.println("双向链表为空--");
return;
}
ListNode2 temp = head.next;
while (temp != null) {
System.out.println(temp);
temp = temp.next;
}
}
总结:双向链表相比较单链表,更加注重自我操作(即,直接对temp指向的结点修改,不依靠temp的前一个结点)。当然所谓自我操作也带来需要注意的地方,需要考虑第一个和最后一个结点,会比较特殊。