这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战”
🎨链表
✨1.链表( Linked List) 介绍
链表是一种物理存储单元上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的。
特点:
1. 链表是以结点形式存储的,是链式存储
2. 每个结点包含data区域和next区域
3. 如上图各个结点并不是连续存储的
4. 链表分带头结点链表和没有带头结点链表,根据实际的需求来确定
带头结点链表逻辑结构
需求:
根据带有头部的单链表,实现商品增删改查,并且也可以针对商品已编号进行排序,完成排行榜
🎉2. 单链表应用
* author:韩国庆
* date:2021/1/23 14:50
* version:1.0
*/
public class GoodsNode {
public int gId;
public String gName;
public double gProce;
//指针指向下一个结点
public GoodsNode next;
public GoodsNode(int gId, String gName, double gProce) {
this.gId = gId;
this.gName = gName;
this.gProce = gProce;
}
}
* author:韩国庆
* date:2021/1/23 15:23
* version:1.0
*/
public class DLLinkedList {
private GoodsNode node = new GoodsNode(0,"",0.0);
/**
* 添加结点
* @param goodsNode
*/
public void add(GoodsNode goodsNode){
GoodsNode temp = node;
while (true){
if (temp.next == null){
break;
}
temp = temp.next;
}
temp.next = goodsNode;
}
/**
* 插入结点按照id编号插入
*/
public void addByOrder(GoodsNode goodsNode){
GoodsNode temp = node;
boolean flag = false;
while (true){
if (temp.next==null){
break;
}
if (temp.next.gId>goodsNode.gId){
break;
}else if (temp.next.gId==goodsNode.gId){//说明已经
flag = true;
break;
}
temp = temp.next;
}
if (flag){
System.out.println("不能添加该商品,已经存在了...");
}else {
goodsNode.next = temp.next;
temp.next = goodsNode;
}
}
/**
* 修改结点
* @param goodsNode
*/
public void updateNode(GoodsNode goodsNode){
if (node.next==null){
System.out.println("链表为空...");
return;
}
GoodsNode temp = node.next;
boolean flag = false;
while (true){
if (temp == null){
break;
}
if (temp.gId == goodsNode.gId){
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.gName = goodsNode.gName;
temp.gProce = goodsNode.gProce;
}else {
System.out.println("没有找到该结点");
}
}
/**
* 删除结点
*
*/
public void delNode(int gId){
GoodsNode temp = node;
boolean flg = false;
while(true){
if (temp.next==null){
break;
}
if (temp.next.gId==gId){
flg = true;
break;
}
temp = temp.next;
}
if (flg){
temp.next = temp.next.next;
}else {
System.out.println("要删除的信息不存在");
}
}
}
🎊3. 常见面试题
给定一个链表,实现倒转链表
if (goodsNode.next==null || goodsNode.next.next == null){
return;
}
GoodsNode point = goodsNode.next;
GoodsNode next = null;
GoodsNode reverseNode = new GoodsNode(0,"",0.0);
while (point !=null){
next = point.next;
point.next = reverseNode.next;
reverseNode.next = point;
point = next;
}
goodsNode.next = reverseNode.next;
}
🎃5. 双向链表应用场景
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
* author:韩国庆
* date:2021/1/25 16:45
* version:1.0
*/
public class GoodsNode2 {
public int gId;
public String gName;
public double gProce;
//指针指向下一个结点
public GoodsNode2 next;
//指针指向上一个结点
public GoodsNode2 pre;
public GoodsNode2(int gId, String gName, double gProce) {
this.gId = gId;
this.gName = gName;
this.gProce = gProce;
}
}
* author:韩国庆
* date:2021/1/25 16:47
* version:1.0
*/
public class TwoLinkedList {
private GoodsNode2 node = new GoodsNode2(0,"",0.0);
/**
*
* @return 返回头结点
*/
public GoodsNode2 returnHeadNode(){
return node;
}
/**
* 添加结点到链表最后面
*/
public void addLast(GoodsNode2 goodsNode2){
GoodsNode2 temp = node;
while (true){
if (temp.next == null){
break;
}
temp = temp.next;
}
temp.next = goodsNode2;
goodsNode2.pre = temp;
}
/**
* 修改结点
*/
public void updateNode(GoodsNode2 newNode){
if (node.next==null){
System.out.println("链表为空");
return;
}
GoodsNode2 temp = node.next;
boolean flg = false;
while (flg){
if (temp == null){
break;
}
if (temp.gId == newNode.gId){
flg = true;
break;
}
temp = temp.next;
}
if (flg){
temp.gName = newNode.gName;
temp.gProce = newNode.gProce;
}else {
System.out.println("没有找到...");
}
}
/**
* 双向链表删除
*/
public void delNode(int gId){
if (node.next == null){
System.out.println("链表为空,无法删除");
return;
}
GoodsNode2 temp = node.next;
boolean flag = false;
while (true){
if (temp == null){
break;
}
if (temp.gId == gId){
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.pre.next = temp.next;
if (temp.next !=null){
temp.next.pre = temp.pre;
}
}else {
System.out.println("要删除的结点不存在");
}
}
}
🎄6.单向环形链表应用场景
约瑟夫问题(约瑟夫环)
设编号为1,2…,n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
💎7. 单向环形链表介绍
采用一个单项环形链表来处理(可以带表头也可以不带表头),具体使用头结点还是不带头结点,后面会具体分析。
约瑟问题示意图
· n=5,即有5个人
· k=1,从第一个人开始报数
· m=2,数2下
*/****\
**** ***@company:* ***北京动力节点**\
****** ***@author:* *****韩国庆******\
***********/**\
***public class** Boy {\
\
**private int** **no**; *//* *编号**\
*****private** Boy **next**; *//* *指向下一个节点,默认依旧为空**\
**\
*****public** Boy(**int** no){\
**this**.**no** = no;\
}\
**public void** setNo(**int** no) {\
**this**.**no** = no;\
}\
**public int** getNo() {\
**return** **no**;\
}\
**public** Boy getNext() {\
**return** **next**;\
}\
**public void** setNext(Boy next) {\
**this**.**next** =
***********/**\
***public class** CircleSingleLinkedList {\
\
*//* *创建一个* *first* *节点,当前没有编号**\
*****private** Boy **first** = **new** Boy(-1);\
\
*//* *加入小孩节点,构建成环形链表**\
*****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* *后移**\
***}\
}\
*//* *根据用户的输入,计算出小孩节点出圈的顺序**\
*
*/****\
**** ***@company:* ***北京动力节点**\
****** ***@author:* *****韩国庆******\
***/***\
**startNo* *表示从第几个小孩开始数数**\
**countNum* *表示数几下**\
**nums* *表示 最初有多少小孩在圈**\
***/**\
*****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**){ *//* *说明小孩指向最后节点**\
*****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());\
}\
}
**public class** TestApp {\
\
**public static void** main(String[] args) {\
*//* *测试,构建环形与遍历是否可行**\
***CircleSingleLinkedList circleSingleLinkedList = **new** CircleSingleLinkedList();\
circleSingleLinkedList.addBoy(5); *//* *添加五个小孩节点**\
***circleSingleLinkedList.showBoy();\
*//* *测试小孩出圈是否正确**\
***circleSingleLinkedList.countBoy(1,2,5); *//* *从第一个开始,每两个小孩出去一次,一共有五个小孩**\
***}\
\
}