数据结构自学笔记(三):链表
单链表
一、概述
链表是有序的列表,但是它在内存中是如下方式来存储:
二、特点
- 链表是以节点的方式储存;
- 每个节点包含data域,next域,指向下一个节点;
- 链表的每一个节点不一定是连续存放的;
- 链表分带头节点的链表和没有头节点的链表,根据实际需求来确定。
三、单链表的创建和添加
3.1添加链表元素时,直接添加到链表尾部;(无序链表)
- 先创建一个head头节点,作用是代表单链表的头;
- 每添加一个节点,就直接接入链表的最后;
- 遍历:通过一个辅助变量,帮助遍历整个单链表。
代码实现:
public class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next;
public HeroNode(int no,String name,String nickname){
this.no = no;
this.name = name;
this.nickname = nickname;
}
//为了显示方便,重写toString
@Override
public String toString() {
return "HeroNode[no= "+ no +",name = "+ name + ",nickname = "+nickname;
}
}
public class SingleLinkedList {
HeroNode head = new HeroNode(0, "", "");
public void add(HeroNode hero) {
HeroNode temp = head;
while (true) {
if (temp.next == null) { //找到单向链表的尾部,将英雄赋给链表尾部;
break;
}
temp = temp.next;
}temp.next = hero;
}
//遍历整个单向链表
public void serch() {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp.no + temp.name + temp.nickname);
//将temp后移
temp = temp.next;
}
}
}
public class Test {
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1,"鲁班大师","智商250");
HeroNode hero2 = new HeroNode(2,"庄周","辅助");
HeroNode hero3 = new HeroNode(3,"嫦娥","祖安法师");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.serch();
}
}
3.2添加链表元素时,根据元素序号将链表元素添加到指定位置
- 首先找到新添加的节点的位置,通过辅助变量,通过遍历来实现;
- 新的节点.next = temp.next;
- 将temp.next = 新的节点;
代码实现:
在上面代码的基础上加入以下方法:
public void addByOrder(HeroNode hero){
HeroNode temp = head;
boolean flag = false;
while (true){
if (temp.next == null){
break;
}
if (temp.next.no > hero.no){
break;
}else if(temp.next.no == hero.no){
flag = true;break;
}else{
temp = temp.next;
}
}
if (!flag) {
hero.next = temp.next;
temp.next = hero;
}else {
System.out.println("待插入的英雄编号已存在,不能插入");
}
}
四、节点的添加
根据节点的序号,来查找该链表元素,修改其属性。注意其节点序号是不能修改的。
在上述代码中添加如下方法实现:
public void update(HeroNode newHeroInf){
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
System.out.println("链表为空");break;
}
if (temp.no == newHeroInf.no){
flag = true;break;
}else {
temp = temp.next;
}
}
if (flag){
temp.name = newHeroInf.name;
temp.nickname = newHeroInf.nickname;
}else {
System.out.println("未找到该节点,不能修改");
}
}
五、删除节点
5.1 思路:
- 先找到需要删除的这个节点的前一个节点;
- temp.next = temp.next.next;
- 被删除的节点将不会有其他引用指向,会被垃圾回收机制回收。
5.2 代码实现:
public void del(int no){
HeroNode temp = head;
boolean flag = false;
while (true){
if (temp.next == null){
System.out.println("数组已空");break;
}
if (temp.next.no == no){
flag = true;break;
}else {
temp = temp.next;
}
}
if (flag){
temp.next = temp.next.next;
}else {
System.out.println("未找到该节点,无法删除");
}
}
双链表
一、概述
双向链表是在单链表的基础上实现的可以双向访问的链表。
相较于单链表,双向链表在“改”、“查”功能上与单链表并无区别,“增”、“删”功能则需要考虑前向指针pre.
二、代码实现
“改”、“查”功能同上述单链表中相应代码,此处不再赘述。
2.1 删除链表元素
public class HeroNode {
int no;
HeroNode next;
HeroNode pre;
HeroNode(int no){
this.no = no;
}
}
public void del(int num){
HeroNode temp = head;
boolean flag = false;
if (temp.next == null){
System.out.println("链表为空");
return;
}
while (true){
if (temp.next == null){
break;
}
if (temp.no == num){
flag = true;
break;
}else {
temp = temp.next;
}
}
if (flag){
temp.pre.next = temp.next;
if (temp.next != null) {
temp.next.pre = temp.pre;
}
}else {
System.out.println("未找到该节点,无法删除");
}
}
2.2 按序添加链表元素
public void addByOder(HeroNode hero){
HeroNode temp = head;
boolean flag = true;
while (true){
if (temp.next ==null){
break;
}
if (temp.next.no > hero.no){ //用需要插入结点的前一个结点来判断
break;
}else if (temp.no == hero.no){
flag = false;break;
}else {
temp = temp.next;
}
}
if (!flag){
System.out.println("该英雄已存在,不能添加");
}else {
if (temp.next != null){ //当temp为头节点时,如果不进行判断会导致空指针异常
temp.next.pre = hero; //因为temp.next会改变,故先将后一个结点的前向指向hero
}
hero.next = temp.next; //再将hero后向指向temp
hero.pre = temp; //再将hero前向指向temp
temp.next = hero; //将temp后向指向hero
}
}
环形链表
一、概念图
二、约瑟夫问题:
设编号为1,2…n的n个人围坐在一圈,约定编号为K的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
我们可以使用循环链表解决这个问题。先构成一个有n个节点的单循环链表,然后由k节点起从1开始计数,计到m时,对应节点从链表中删除,然后再从被删除节点的下一个节点又从1开始计数,知道最后一个节点从链表中删除,算法结束。
如概念图所示,假设n=5,k=1,m=2,则出队列的顺序为:2 -> 4 ->1 -> 5 -> 3.
创建环形链表及解决约瑟夫问题的代码如下:
public class Boy { //定义Boy用来表示约瑟夫问题中的人
private int num;
private Boy next;
Boy(int num){
this.num = num;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
创建环形链表的步骤:
1.用Boy型变量指针boy创建每一个新的Boy对象,first指针永远指向第一个节点,定义辅助指针curBoy。
2.当创建第一个节点时,让第一个节点的next指向自己;
3.创建之后的节点时,将新创建的Boy节点的next指向first,curBoy(即上一个节点)的next指向Boy,在将curBoy下移,即curBoy = Boy,Boy再去承接下一个新的节点。
取出节点的步骤:
取出过程中,K为开始的序号,m为每次出节点的间隔,nums为儿童的数目。
1.先创建一个辅助指针helper,指向first节点的前一个结点;
2.将first和helper分别移向对应位置K,K的前一个节点;
3.再将first和helper分别移向需要取出的位置m,和m的前一个结点;
4.进行取出,先将原本指向应该取出节点m的first移向m的下一个节点,再操作指向的m-1节点的helper,使其next指向移动到m+1的first,此时m点就被取出了,且环形链表重新闭合。
图为取出2节点时的情况:
public class CircleSingleLinkedList {
private Boy first = null; //定义头指针
//创建环形链表
public void addBoy(int nums){
if (nums < 1){
System.out.println("输入小孩子的值不正确");
return;
}
Boy curBoy = null; //定义辅助指针
for (int i = 1; i <= nums; i++) {
Boy boy = new Boy(i);
if (i ==1){
first = boy;
first.setNext(first);
curBoy = first;
}else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
public void showBoy(){
if (first == null){
System.out.println("链表为空");
return;
}
Boy curBoy = first;
while (true){
System.out.println(curBoy.getNum());
if (curBoy.getNext() == first){
break;
}
curBoy = curBoy.getNext();
}
}
//K为开始的序号,m为每次出节点的间隔,nums为儿童的数目
public void coutBoy(int k,int m,int nums){
if (first == null || k < 1 || k > nums){
System.out.println("输入的序号有误");
return;
}
//将help指针指向first的后一个节点
Boy helper = first; //定义辅助指针,指向firsr的后一个节点
while (true){
if (helper.getNext() == first){
break;
}
helper = helper.getNext(); //让help指向first的前一个节点
}
//让first到达指定的第一个节点,help到达指定第一个节点的前一个节点
for (int i = 0; i < k - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
//开始抽取节点
while (true){
if (helper == first){ //当helper == first时,只剩最后一个节点
break;
}
//让两个指针到达需要取出节点的位置
for (int j = 0; j < m-1; j++) {
first = first.getNext();
helper = helper.getNext();
}
//退出for循环时已经到达需要取出的节点的位置,进行取出操作
System.out.println(first.getNum());
first = first.getNext();
helper.setNext(first);
}
System.out.println(first.getNum());
}
}