数据结构——单向链表
单向链表
链表通常是指单向链表,它包含多个结点,每个结点有一个指向后继元素的指针。表中最后一个结点的next指针的值为NULL,表示该链表的结束。结构如下图所示:

下面的代码时一个链表的类型声明:
public class ListNode {
private int data;
private ListNode next;
public ListnNode(int data){
this.data = data;
}
//getter and setter ......
}
链表的基本操作
- 遍历链表
- 插入元素
- 删除元素
链表的遍历
遍历链表需要完成以下步骤。
- 沿指针遍历
- 遍历时显示结点的内容
- 当next指针的值为NULL时,结束遍历
获取链表长度和遍历链表的伪代码如下:
/**
* 获取链表长度
*/
int ListLength(ListNode headNode){
int length = 0;
ListNode currentNode = headNode;
while(currentNode != null){
length++;
currentNode = currentNode.getNext();
}
return length;
}
/**
* 遍历链表
*/
void printListNode(ListNode headNode){
ListNode currentNode = headNode;
while(currentNode != null){
print(currentNode.getData());
currentNode = currentNode.getNext();
}
}
在单向链表的开头插入结点
如果要在当前表头插入一个新的结点,只需要修改一个next指针__(新结点的next指针)__,可以通过以下俩个步骤来完成:
-
更新新结点next指针,使其指向当前表头的结点。
![]()
-
更新表头指针的值,使其指向新结点。
![]()
在单向链表的结尾插入结点
如果需要在表尾插入新结点,则需要修改俩个next指针(最后一个next指针和新结点的next指针)。
-
新节点的next指针指向NULL。
![]()
-
最后一个结点的next指针指向新结点。
![]()
在单向链表的中间插入结点
如果给定插入的位置,在这种情况下,需要修改俩个指针(指定位置前一个结点的next指针和新结点的next指针)。
- 首先找到要插入位置的前一个结点,例如要在2位置插入,那么应该先找到1位置的结点,新结点的next指针应该改指向1位置结点的下一个结点。
- 然后将1位置的next指针指向新结点。
![]()
上述三种插入方式的伪代码如下:
ListNode InsertInLinkedList(ListNode headNode, ListNode nodeToInsert, int position){
if(headNode == null){ //若链表为空,插入
return nodeToInsert;
}
int size = ListLenth(headNode); //获取链表长度
if(position > size+1 || position < 1){ //如果给定位置超出链表大小或者小于1则打印警告
print("给定位置非法,位置的有效值为1~"+size+1);
return headNode;
}
if(position == 1){ //在表头插入
nodeToInsert.setNext(headNode);
return nodeToInsert;
}else{
//在链表中间或末尾插入
ListNode previousNode = headNode;
int count = 1;
while(count < position-1){
previousNode = previousNode.getNext();
count++;
}
ListNode currentNode = previousNode.getNext();
nodeToInsert.setNext(currentNode);
previousNode.setNext(nodeToInsert);
}
return headNode;
}
上述的几种插入方式可以分开单独的方法去实现。文章结尾有关于本文提及到的所有方法的demo,可供参考。
单向链表的删除
单向链表的删除也有三种情况:
- 删除链表的表头
- 删除链表的表尾
- 删除链表中间的结点
删除单向链表的第一个结点
删除单向链表的第一个结点可以分为两步实现:
- 创建一个临时结点,它指向表头指针所指的结点。
- 修改表头指针所指的值,使其指向下一个结点,并移除临时结点。
总体思想就是将指向表头的指针移动到表头指向的结点,然后删除表头
ListNode currentNode = headNode.getNext();
headNode = null;
return currentNode;
删除单向链表最后一个结点
这种情况下将删除链表的最后一个结点。需要三个步骤来实现:
-
遍历链表,在遍历时要保存前驱结点(前一次经历过的结点)的位置。当遍历到链表的表尾时,将有俩个指针——表尾(tail)的指针和前驱结点的指针.
![]()
-
将前驱结点的next指针更新为NULL。
![]()
-
移除表尾结点。
![]()
删除单向链表中间的结点
在这种情况下,被删除的结点总是位于俩个结点之间,因此,不需要更新表头和表尾的指针,该删除操作可通过两步实现:
-
遍历链表,遍历时保存前驱结点的位置,当找到要删除的结点,将前驱结点next指针更新为被删除结点的next指针的值。
![]()
-
移除需要删除的当前结点
![]()
ListNode DeleteNodeFromLinkedList(ListNode headNode, int position){
int size = getLinkedListLength(headNode);
if(position > size || position < 1){
print("删除位置非法,位置应该介于1~"+size+"之间");
}
if(position == 1){ //删除单向链表的表头结点
ListNode currentNode = headNode.getNext();
headNode = null;
}else{ //删除中间或表尾结点
ListNode previousNode = headNode;
int count = 1;
while(count < position){
previousNode = previousNode.getNext();
count++;
}
ListNode currentNode = previousNode.getNext();
previousNode.setNext(currentNode.getNext());
currentNode = null;
}
return headNode;
}
上面的三种删除方式也可以单独使用三个方法去去实现,实现方式在文章结尾的完整demo中。
上面就是对单向链表的简单的介绍,在文章最后通过一个demon将上述的操作整体的练习一遍。代码分为三个文件——ListNode.java、SingleList.java、Test.java。
代码DEMON部分
ListNode.java
package bokeyuan.linked;
/**
* 链表结构
*/
public class ListNode {
private Object data;
private ListNode next;
public ListNode(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
SingleList.java
package bokeyuan.linked;
public class SingleList {
private ListNode headNode;
/*------------------------------------------------单向链表三种插入方法-------------------------------------------*/
/**
* 单向链表插入——从头部插入
*
* @param data 新结点数据
*/
public void addFromHead(Object data) {
ListNode newNode = new ListNode(data); // 创建一个新的结点
ListNode currentNode = headNode; // 保存道歉head结点的值
headNode = newNode; // 将新结点作为头部结点
if (currentNode != null)
newNode.setNext(currentNode); // 将新头部结点的next指针指向上面保存的旧的head结点值
}
/**
* 单向链表插入——从表尾插入
*
* @param data 新结点数据
*/
public void addFromTail(Object data) {
ListNode newNode = new ListNode(data);
if (headNode == null) { //如果headNode为空,则直接将新结点赋给头部
headNode = newNode;
return;
}
ListNode tempNode = headNode; //创建临时结点
while(tempNode.getNext() != null) { //遍历结点,找到尾部结点后将它的next指针更新为新结点
tempNode = tempNode.getNext();
}
tempNode.setNext(newNode);
}
/**
* 指定位置插入
* @param data 将要插入的数据
* @param position 指定的位置
*/
public void add(Object data, int position) {
ListNode newNode = new ListNode(data);
if(headNode == null) {
headNode = newNode;
return;
}
int size = listLength(); //获取链表的长度
if(position > size || position < 0) {
System.err.println("指定位置不合法,请给定0~"+(size-1)+"的值");
}
if(position == 0) { //如果指定了0则调用从头部插入方法
addFromHead(data);
return;
}
ListNode previousNode = headNode; //前驱结点
int count = 1; //注意这里应该从1开始,因为0的时候走的是上面第一个if语句,如果这里count从0开始的画会报空指针错误
while(position != count) {
count++;
previousNode = previousNode.getNext();
}
ListNode currentNode = previousNode.getNext(); //获取到前驱结点的next指针指向的结点
newNode.setNext(currentNode); //新结点的next指针指向前驱结点next指针指向的值
previousNode.setNext(newNode); //将前驱结点的next指针更新为新结点
}
/*------------------------------------------------单向链表三种删除方法-------------------------------------------*/
/**
* 从头部删除链表元素
*/
public void removeFromHead() {
if(headNode != null) {
ListNode tempNode = headNode; //创建临时结点指向头部结点
headNode = tempNode.getNext(); //修改头部的值为上一步中的临时结点
}
}
/**
* 从尾部删除链表元素
*/
public void removeFromTail() {
if(headNode.getNext() == null) { //如果当前只有一个结点
headNode = null;
}
if(headNode != null && headNode.getNext() != null) {
ListNode previousNode = headNode; //前驱结点
while(previousNode.getNext().getNext() != null) { //遍历寻找表尾的前驱结点
previousNode = previousNode.getNext();
}
previousNode.setNext(null); //将前驱结点的next指针更新为Null
}
}
/**
* 根据给定位置删除链表元素
* @param position
*/
public void remove(int position) {
int size = listLength();
if(position > size-1 || position < 0) {
System.err.println("指定索引非法,请在0~"+(size-1)+"范围内指定");
}
if(headNode.getNext() == null) { //如果只有一个结点直接将头部结点赋值为null
headNode = null;
}
if(position == 0) { //从头部删除
removeFromHead();
}
if(headNode != null && headNode.getNext() != null) {
int count = 1;
ListNode previousNode = headNode; //前驱结点
while(position != count) { //寻找前驱结点
count++;
previousNode = previousNode.getNext();
}
ListNode currentNode = previousNode.getNext(); //创建临时结点,值为前驱结点的next指针所指向的值
previousNode.setNext(currentNode.getNext()); //更新前驱结点next指针指向的值为上面的临时结点
}
}
/*------------------------------------------------单向链表获取长度、打印链表、获取链表指定位置值-------------------------------------------*/
/**
* 获取单向链表的长度
*
* @return 返回单向链表的长度
*/
public int listLength() {
ListNode tempNode = headNode;
int count = 0;
while (tempNode != null) {
count++;
tempNode = tempNode.getNext();
}
return count;
}
/**
* 打印链表
*/
public void printList() {
ListNode tempNode = headNode;
if(tempNode == null) {
System.out.println("空链表");
}
while (tempNode != null) {
System.out.print(" " + tempNode.getData());
tempNode = tempNode.getNext();
}
System.out.println();
}
/**
* 通过索引获取链表的值
* @param index 索引值 从0开始
* @return 返回对应索引的数据
*/
public Object get(int index) {
int size = listLength();
if(index > size-1 || index < 0) {
System.err.println("索引非法,请输入0~"+(size-1)+"范围内的值");
}
ListNode tempNode = headNode;
int count = 0;
while(index != count) {
tempNode = tempNode.getNext();
count++;
}
return tempNode.getData();
}
}
Test.java
package bokeyuan.linked;
public class Test {
public static void main(String[] args) {
SingleList list = new SingleList();
for(int i=0; i<10; i++) {
list.add("item-"+i,i);
}
//打印测试
System.out.println("打印链表");
list.printList();
//从头部添加测试
System.out.println("从头部给链表添加数据");
list.addFromHead("fromHeadFunction");
list.printList();
//从尾部添加
System.out.println("从尾部给链表添加数据");
list.addFromTail("fromTailFunction");
list.printList();
//从指定位置添加值
System.out.println("从指定位置添加值");
list.add("customPosition", 2);
list.printList();
//获取指定位置值
System.out.println("获取指定位置值");
String data = (String) list.get(2);
System.out.println(data);
//从头部删除
System.out.println("从头部删除");
list.removeFromHead();
list.printList();
//从尾部删除
System.out.println("从尾部删除");
list.removeFromTail();
list.printList();
//指定位置删除
System.out.println("指定位置删除");
list.remove(3);
list.printList();
//获取链表长度
System.out.println("此时链表长度为");
System.out.println(list.listLength());
}
}
运行结果:
打印链表
item-0 item-1 item-2 item-3 item-4 item-5 item-6 item-7 item-8 item-9
从头部给链表添加数据
fromHeadFunction item-0 item-1 item-2 item-3 item-4 item-5 item-6 item-7 item-8 item-9
从尾部给链表添加数据
fromHeadFunction item-0 item-1 item-2 item-3 item-4 item-5 item-6 item-7 item-8 item-9 fromTailFunction
从指定位置添加值
fromHeadFunction item-0 customPosition item-1 item-2 item-3 item-4 item-5 item-6 item-7 item-8 item-9 fromTailFunction
获取指定位置值
customPosition
从头部删除
item-0 customPosition item-1 item-2 item-3 item-4 item-5 item-6 item-7 item-8 item-9 fromTailFunction
从尾部删除
item-0 customPosition item-1 item-2 item-3 item-4 item-5 item-6 item-7 item-8 item-9
指定位置删除
item-0 customPosition item-1 item-3 item-4 item-5 item-6 item-7 item-8 item-9
此时链表长度为
10

链表通常是指单向链表,它包含多个结点,每个结点有一个指向后继元素的指针。
表中最后一个结点的next指针的值为NULL,表示该链表的结束。
本文介绍了单向链表的基本操作——插入、删除、遍历等操作。









浙公网安备 33010602011771号