一.链表的概念及结构
1.链表的概念2.链表的分类 二.单向不带头非循环链表
1.创建节点类型2.头插法3.尾插法4.打印单链表5.查找key是否在单链表中6.得到单链表的长度7.任意位置插入,第一个数据节点为0号下标8.删除第一次出现关键字为key的节点9.删除所有值为key的节点10.清空单链表 三.双向不带头循环链表
1.创建节点类型2.头插法3.尾插法4.打印双链表5.查找key是否在双链表中6.得到双链表的长度7.任意位置插入,第一个数据节点为0号下标8.删除第一次出现关键字为key的节点9.删除所有值为key的节点10.清空双链表 四.顺序表和链表的区别
1.从组织上看2.从 *** 作上看
一.链表的概念及结构 1.链表的概念链表是一种物理存储结构上非连续的存储结构。数据元素中的逻辑顺序是通过链表中的引用链接次序实现的
2.链表的分类链表情况非常多样,组合起来共有八种
单向、双向带头、不带头循环、非循环
但是这里我们重点讲两种:单向不带头非循环链表、双向不带头非循环链表
二.单向不带头非循环链表
1.创建节点类型
这里我们创建一个ListNode类来作为节点类型,包含两个成员变量:val域用来储存数据,next用来存储下一个节点的地址。
再创建一个带参的构造方法来实例化对象,同时给val赋值,next的默认值是null。接下来我们用代码来实现一下:
class ListNode{ public int val; public ListNode next;//next存储的是下一个节点的地址,所以他的类型是一个节点类型 public ListNode(int val){ this.val = val; } }
然后我们在MylinkedList里创建一个节点类型:head。可能大家会有疑问了,为什么在MylinkedList里创建,而不是在节点里创建呢?因为head是链表的头,而不是节点的头,节点只有两个属性:next和val。
准备工作做完,我们就可以具体实现链表的增删查改等 *** 作啦!
头插法就是从链表的头部插入节点node,然后使新节点node成为头节点head
具体代码实现如下:
//头插法 public void addFirst(int data){ ListNode node = new ListNode(data); node.next = this.head; this.head = node; }3.尾插法
尾插法跟头插法的不同之处在于,尾插法的第一次插入必须判断链表是否为空,即头节点是否为null,如果为null,那么新插入的节点直接变成头节点即可。除此之外,我们需要引入一个局部变量:cur来遍历链表,当cur.next为空的时候,说明此时的cur就是尾节点,我们只需要在cur后面插入新节点node即可
具体的代码实现如下:
//尾插法 public void addLast(int data){ ListNode node = new ListNode(data); if (this.head == null){ this.head = node;//说明是第一次插入,直接让node变成head }else{ ListNode cur = this.head; while(cur.next != null){ cur = cur.next; } // cur走完了所有节点 : cur.next = null cur.next = node; } }4.打印单链表
链表的打印和顺序表的打印大同小异,只需要遍历链表就行了。不过需要注意的是,我们不能用头节点head来遍历,因为遍历完head就找不到了,所以我们需要用局部变量cur来代替head遍历
具体的代码实现如下:
//打印链表长度 public void display(){ ListNode cur = this.head; while( cur != null){ System.out.print(cur.val + " "); cur = cur.next; } System.out.println(); }5.查找key是否在单链表中
传入关键字key,使用局部变量cur遍历单链表,当cur.val等于key时,说明单链表中包含key,返回true,否则遍历完没找到,返回false
具体的代码实现如下:
//查找关键字key是否包含在单链表当中 public boolean contains(int key){ ListNode cur = this.head; while(cur !=null){ if (cur.val == key){ return true; } cur = cur.next; } return false; }6.得到单链表的长度
跟顺序表做法大同小异,还是用cur遍历单链表,同时创建一个计数器count,只要节点不为null,count++,最后返回count的值就是该单链表的长度
具体的代码实现如下:
//得到单链表的长度 public int size(){ int count = 0; ListNode cur = this.head; while(cur!=null){ count++; cur = cur.next; } return count; }7.任意位置插入,第一个数据节点为0号下标
跟顺序表类似,插入的时候,我们都要判断其位置是否合法。然后我们需要创建一个findIndex方法用于查找插入位置的前一个节点
具体的代码实现如下:
//先找到index-1位置 节点的地址,就是cur位置 public ListNode findIndex(int index){ ListNode cur = this.head; while(index-1 != 0){ cur=cur.next; index--; } return cur; } //任意位置插入,第一个数据节点为0号下标 public void addIndex(int index,int data){ if (index<0 || index>size()) {//先判断index坐标的合法性 System.out.println("index位置不合法!"); return; } if (index == 0){//头插法 addFirst(data); return; } if (index == size()){//尾插法 addLast(data); return; } ListNode cur = findIndex(index); ListNode node = new ListNode(data); node.next = cur.next; cur.next = node; }8.删除第一次出现关键字为key的节点
删除的时候,我们要先判断单链表是否为空(头节点是否为null)。如果不为空,我们要看需要删除的节点是否为头节点,如果是,我们直接将头节点的下一节点设置为头节点。如果要删除的节点不是头节点,我们可以写一个方法来寻找该节点的前驱节点,然后将要删除的节点的下一节点del.next赋值给前驱节点的下一节点cur.next
具体的代码实现如下:
//先找到key节点的前驱节点 public ListNode searchPrev(int key){ ListNode cur = this.head; while (cur.next != null){ if (cur.next.val == key){//如果找到该前驱节点,返回前驱节点cur return cur; } cur = cur.next;//未找到就继续遍历链表 } return null;//如果循环完都没找到,就返回null } //删除第一次出现关键字为key的节点 public void remove(int key){ if (this.head == null){ System.out.println("链表为空,不能删除!"); return; } if (this.head.val == key){//要删除的位置就在头节点 this.head = this.head.next; return; } ListNode cur = searchPrev(key);//调用刚刚写的函数来寻找前驱cur if (cur == null){ System.out.println("没有你要删除的节点!"); return; } ListNode del = cur.next; cur.next = del.next; }9.删除所有值为key的节点
首先还是要判断单链表是否为空,如果为空则返回null。然后设置prev为cur的前驱,cur从head.next开始遍历,遇到cur.val为key值时,删除该节点然后继续遍历,遍历完后再来处理头节点,判断head是否为key值,是的话进行删除 *** 作即可
具体代码实现如下:
//删除所有值为key的节点 public ListNode removeAllKey(int key){ if (this.head == null) return null; ListNode prev = this.head; ListNode cur = this.head.next; while (cur!= null){ if (cur.val == key){//假如cur节点是要删除的节点,将cur节点删去再继续遍历 prev.next = cur.next; cur = cur.next; }else{//cur节点不是要删除的节点,继续往后遍历 prev = cur; cur = cur.next; } } //最后处理头,判断头节点的val域是不是key值 if (this.head.val == key){ this.head = this.head.next; } return this.head; }10.清空单链表
暴力清空:直接将头节点置空
//清空单链表 public void clear(){ this.head = null; }
温柔清空:将节点一个一个释放
//清空单链表 public void clear(){ while (this.head != null){ ListNode curNext = head.next; this.head.next = null; this.head = curNext; } }三.双向不带头循环链表 1.创建节点类型
这里我们创建一个ListNode类来作为节点类型,包含三个成员变量:val域用来储存数据,next用来存储下一个节点的地址,prev用来存储上一个节点的地址。
再创建一个带参的构造方法来实例化对象,同时给val赋值,next和prev的默认值是null。接下来我们用代码来实现一下:
class ListNode{ public int val; public ListNode prev; public ListNode next; public ListNode(int val){ this.val = val; } }
然后,我们在MylinkedList里创建两个节点类型,分别是head和last,head指向双链表的头节点,last指向双链表的尾节点。下面,我们来进行双链表的增删查改!
2.头插法同样的,我们还是要先判断第一次插入节点node时链表是否为空,如果链表为空,那么我们的head和last都要指向node。插入时,我们可以画图来理解:
具体的代码实现如下:
//头插法 public void addFirst(int data){ ListNode node = new ListNode(data); //先判断是不是第一次插入 if (this.head == null){ this.head = node; this.last = node; }else { node.next = this.head; this.head.prev = node; this.head = node;//最后将头节点改为node } }3.尾插法
跟头插法一样,第一次插入节点node时同样要考虑链表是否为空。为空则将head节点和last节点都绑定为node节点即可。不为空时,我们同样通过画图理解来更改位置,最后将last节点改为node节点即可
具体的代码实现如下:
//尾插法 public void addLast(int data){ ListNode node = new ListNode(data); //跟头插法一样,还是要先判断是不是第一次插入 if (this.head == null){ this.head = node; this.last = node; }else{ this.last.next = node; node.prev = this.last; this.last = node; } }4.打印双链表
跟单链表做法相同,使用局部变量cur来代替head遍历双链表
具体的代码实现如下:
//打印双链表 public void display(){ //和单链表的打印方式一样 ListNode cur = this.head; while (cur != null){ System.out.print(cur.val + " "); cur = cur.next; } System.out.println(); }5.查找key是否在双链表中
做法也跟单链表相同,使用局部变量cur代替head遍历链表,cur.val的值等于key值时就返回true
具体的代码实现如下:
//查找是否包含关键字key是否在双链表当中 public boolean contains(int key){ ListNode cur = this.head; while (cur != null){ if (cur.val == key){ return true; } cur = cur.next; } return false; }6.得到双链表的长度
做法还是与单链表相同。设置一个计数器count,局部变量cur来代替head遍历,cur不为0时,count++,最后返回count就是双链表的长度
具体的代码实现如下:
//打印双链表 public void display(){ //和单链表的打印方式一样 ListNode cur = this.head; while (cur != null){ System.out.print(cur.val + " "); cur = cur.next; } System.out.println(); }7.任意位置插入,第一个数据节点为0号下标
插入的时候,我们要先判断index位置的合法性。然后我们创建一个findIndex方法来寻找要插入的位置。注意,跟单链表不同,单链表是寻找插入位置的前驱!
具体的代码实现如下:
//找到要插入节点的位置 public ListNode searchIndex(int index){ ListNode cur = this.head; while (index != 0) { cur = cur.next; index--; } return cur; } //任意位置插入,第一个数据节点为0号下标 public void addIndex(int index,int data){ ListNode node = new ListNode(data); if (index<0 || index>size()){//先判断index位置的合法性 System.out.println("该位置不合法!"); }if (index == 0){//头节点插入,采用头插法 addFirst(data); }if (index == size()){//尾节点插入,采用尾插法 addLast(data); } ListNode cur = searchIndex(index); node.next = cur.prev.next; cur.prev.next = node; node.prev = cur.prev; cur.prev = node; }8.删除第一次出现关键字为key的节点
首先还是判断链表是否为空,不为空我们再去寻找删除的节点,要删除的节点有三种情况:
要删除的节点在头节点:直接将头节点的下一节点设置为新的头节点,再将新头节点的前驱置为空即可要删除的节点在中间节点:只需要通过画图,然后改四个位置即可要删除的节点在尾节点:将尾节点前驱的next置为尾节点的next(也就是null),再将尾节点的前驱设为新的尾节点
具体的代码实现如下:(这段代码可能难理解,建议画图自己写一遍)
//删除第一次出现关键字为key的节点 public void remove(int key){ ListNode cur = this.head; while (cur != null) { if (cur.val == key) { if (cur == head){//首先判断要删除的节点是不是头节点 this.head = this.head.next;//先将头节点往后移一位 if (head != null){//如果双链表不是只有一个节点 this.head.prev = null;//再将现在头节点的前驱置为空 }else{//如果双链表只有一个节点,即head为空了 this.last = null;//要把last也置为空 } }else { cur.prev.next = cur.next;//将cur的next,赋给cur前驱的next if (cur.next != null) {//说明不是尾节点,是中间位置 cur.next.prev = cur.prev; }else{//说明是尾节点,只需要将last往前移一位 this.last = this.last.prev; } } return; } cur = cur.next; } }9.删除所有值为key的节点
我们在上一段代码发现删除完一个节点后就不再执行了。既然要删除所有的节点,那我们删掉return即可,即代码删除完一个节点后不返回,继续执行
具体的代码实现如下:
//删除所有值为key的节点 public void removeAllKey(int key){ ListNode cur = this.head; while (cur != null) { if (cur.val == key) { if (cur == head){//首先判断要删除的节点是不是头节点 this.head = this.head.next;//先将头节点往后移一位 if (head != null){//如果双链表不是只有一个节点 this.head.prev = null;//再将现在头节点的前驱置为空 }else{//如果双链表只有一个节点,即head为空了 this.last = null;//要把last也置为空 } }else { cur.prev.next = cur.next;//将cur的next,赋给cur前驱的next if (cur.next != null) {//说明不是尾节点,是中间位置 cur.next.prev = cur.prev; }else{//说明是尾节点,只需要将last往前移一位 this.last = this.last.prev; } } //删完cur继续往后走,不return } cur = cur.next; } }10.清空双链表
暴力清空:将头节点和尾节点都置为空温柔清空:先将head一个一个清空,最后将last也置空
//清空双向链表 public void clear(){ while (head != null) { ListNode curNext = head.next; head.next = null; head.prev = null; head = curNext; } last = null;//最后将last也全部置为空 }四.顺序表和链表的区别 1.从组织上看
顺序表底层是一个数组,是逻辑上和物理上都连续的链表是一个由若干节点组成的数据结构,逻辑上是连续的,但是物理/内存上不一定连续 2.从 *** 作上看
顺序表适合查找相关 *** 作,因为可以使用下标直接获取某一位置的元素链表适合需要频繁插入、删除 *** 作。不需要像顺序表一样移动元素,只需要修改指向顺序表满了后还需要扩容,扩容空间也不一定能完全利用,空间利用率不高。而链表随用随取,不用担心空间的浪费
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)