目录
单链表 (线性链表)
概述:链式存储表示每个数据元素a[i]时,除了存储a[i]本身信息之外,还需要一个存储指示其后继元素a[i+1]存储位置的指针。
包括两个域:存储数据元素的域称为数据域,存储直接后继存储地址的域称为指针域。
空链表:head=NULL
图示:
单链表的基本运算
头插法
概述:从一个空表开始,重复读入数据,生成新结点,将读入的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止。
图解:
尾插法
概述:将新结点插入在当前链表的表尾上,因此需要增设一个尾指针rear,使其始终指向链表的尾结点。
图解:
查找运算
按结点序号查找
概述:从链表第一个结点开始,p指向当前结点,j为计数器,初始值为1,当p扫描下一个结点时,计数器加1。当j=i时,指针p所指向的结点就是要找的结点。平均时间复杂度:O(n)
按结点值查找
概述:从链表的开始结点出发,顺链逐个将结点的值和给定的值k进行比较,若遇到相等的值,则返回该结点的储存位置,否则返回NULL。时间复杂度:O(n)
插入运算
概述:先使p指向a(i-1)的位置,然后生成一个数据域值为x的新结点*s,再进行插入操作。
图解:
时间复杂度:O(n)
删除运算
概述:先使p指向第i-1个结点,然后再使得p—>next指向第i+1个结点,再将第i个结点释放掉。
图解:
时间复杂度:O(n)
代码实现
一、结点类
//链表结点类
public class Node {
//数据域
private Object data;
//指针域
private Node next;
public Node() {
}
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
@Override
public String toString() {
return "\nNode{" +
"data=" + data +
", next=" + next +
'}';
}
}
二、链表类
//链表类
public class LinkList {
//头结点
Node head;
//链表长度
int size;
//构造方法
public LinkList() {
head = null;
size = 0;
}
@Override
public String toString() {
return "LinkList{" +
"head=" + head +
", size=" + size +
'}';
}
}
三、操作类
//操作类
public class SingleLinkedList {
//定义一个单链表
static LinkList linkList = new LinkList();
//定义一个尾指针
static Node rear = linkList.head;
//头插法建表
public static void CreateListF(Node node) {
//申请新结点
Node n = new Node();
//数据域赋值
n.setData(node.getData());
//指针域赋值
n.setNext(linkList.head);
//头指针指向新结点
linkList.head = n;
//链表长度加1
linkList.size++;
}
//尾插法建表
public static void CreateListR(Node node) {
//申请新结点
Node n = new Node();
//数据域赋值
n.setData(node.getData());
//判断链表是否为null,并插入新结点
if (linkList.head == null) {
linkList.head = n;
} else rear.setNext(n);
//尾指针指向新结点
rear = n;
//终端结点指针域置空
if (rear.getNext() != null)
rear.setNext(null);
//链表长度加1
linkList.size++;
}
//遍历
public static void ListShow() {
//定义一个新结点,用于循环赋值显示
Node n = linkList.head;
//循环链表
for (int i = 0; i < linkList.size; i++) {
//输出新结点的数据值
System.out.println(n.getData());
//新结点指向下一个结点
n = n.getNext();
}
}
//查找:按结点序号查找,返回该结点
public static Node GetNodeI(int i) {
//判断查找的序号是否合法
if (i <= 0 || i > linkList.size) {
System.out.println("position error!");
}
//定义一个新结点并赋值
Node n = linkList.head;
//序号无负数,从1开始
for (int j = 1; j <= linkList.size; j++) {
if (i == j) {
break;
}
//新结点指向下一个结点
n = n.getNext();
}
return n;
}
//查找:按结点值查找,参数为结点值
public static Node GetNodeK(Object n) {
//判断链表是否为空
if (linkList.size == 0) {
System.out.println("LinkList is null!");
}
//判断结点值是否与头结点相等
if (n == linkList.head.getData()) {
return linkList.head;
}
//定义一个新结点并赋值
Node newNode = linkList.head;
while (linkList.size > 0) {
if (newNode.getData() == n) {
return newNode;
}
//新结点指向下一个结点
newNode = newNode.getNext();
linkList.size--;
}
return null;
}
//插入:将值为x的新结点插入到第i个结点的位置上
public static void InsertList(int i, Object x) {
//判断插入的位置是否合法
if (i <= 0 || i > linkList.size) {
System.out.println("position error!");
}
//链表长度加1
linkList.size++;
int k = 1;
//创建新结点并赋值
Node p = linkList.head;
//使p指向i-1个结点
while (p != null && k < i - 1) {
p = p.getNext();
k++;
}
//申请一个新结点
Node newNode = new Node();
//数据域赋值
newNode.setData(x);
//指针域赋值
newNode.setNext(p.getNext());
//i-1个结点指向新节点
p.setNext(newNode);
}
//删除:将链表中的第i个结点删除,并返回该值
public static Object DeleteList(int i) {
//判断要删除的位置是否合法
if (i <= 0 || i > linkList.size) {
System.out.println("position error!");
}
int n = 1;
//创建新结点并赋值
Node p = linkList.head;
//使p指向i-1个结点
while (p != null && n < i - 1) {
p = p.getNext();
n++;
}
//定义一个新结点,指向第i个结点
Node s = p.getNext();
//使p的下一个结点指向i+1个结点
p.setNext(s.getNext());
//保存被删除结点的值
Object x = s.getData();
linkList.size--;
return x;
}
}
四、测试类
4.1 建立单链表测试
4.1.1 头插法建表
public class SingleLinkedListTest { public static void main(String[] args) { //创建结点值 Node n1 = new Node("qq", null); Node n2 = new Node("ww", null); Node n3 = new Node("ee", null); Node n4 = new Node("rr", null); //头插法 SingleLinkedList.CreateListF(n1); SingleLinkedList.CreateListF(n2); SingleLinkedList.CreateListF(n3); SingleLinkedList.CreateListF(n4); System.out.println(SingleLinkedList.linkList); } }
运行结果:
4.1.2 尾插法建表
public class SingleLinkedListTest { public static void main(String[] args) { //创建结点值 Node n1 = new Node("qq", null); Node n2 = new Node("ww", null); Node n3 = new Node("ee", null); Node n4 = new Node("rr", null); //尾插法 SingleLinkedList.CreateListR(n1); SingleLinkedList.CreateListR(n2); SingleLinkedList.CreateListR(n3); SingleLinkedList.CreateListR(n4); System.out.println(SingleLinkedList.linkList); } }
运行结果:
4.2 基本操作测试
说明:上边使用的头插法和尾插法是为了创建链表,只需要选择其中的一种创建方式即可。尾插法创建出来的链表跟我们插入的顺序是一样的,更符合实际,为了测试时方便查验,下边对链表的测试都是基于尾插法建立的表进行测试的。
public class SingleLinkedListTest { public static void main(String[] args) { //创建结点值 Node n1 = new Node("qq", null); Node n2 = new Node("ww", null); Node n3 = new Node("ee", null); Node n4 = new Node("rr", null); //尾插法 SingleLinkedList.CreateListR(n1); SingleLinkedList.CreateListR(n2); SingleLinkedList.CreateListR(n3); SingleLinkedList.CreateListR(n4); System.out.println(SingleLinkedList.linkList); System.out.println("---------------------------------"); //遍历 System.out.println("链表元素遍历为:"); SingleLinkedList.ListShow(); System.out.println("---------------------------------"); //查找:按结点序号 Node node = SingleLinkedList.GetNodeI(1); System.out.println("结点序号为1的元素值为:" + node.getData()); System.out.println("---------------------------------"); //查找:按结点值查找 Node node1 = SingleLinkedList.GetNodeK("ww"); System.out.println("元素值是‘ww’的结点为:" + node1); System.out.println("---------------------------------"); } }
运行结果:
4.3 插入删除测试
public class SingleLinkedListTest { public static void main(String[] args) { //创建结点值 Node n1 = new Node("qq", null); Node n2 = new Node("ww", null); Node n3 = new Node("ee", null); Node n4 = new Node("rr", null); //尾插法 SingleLinkedList.CreateListR(n1); SingleLinkedList.CreateListR(n2); SingleLinkedList.CreateListR(n3); SingleLinkedList.CreateListR(n4); System.out.println(SingleLinkedList.linkList); System.out.println("---------------------------------"); //插入 SingleLinkedList.InsertList(3, "66"); //遍历 System.out.println("插入结点之后遍历链表为:"); SingleLinkedList.ListShow(); System.out.println("---------------------------------"); //删除 Object o = SingleLinkedList.DeleteList(4); System.out.println("被删除的元素值为:"+o); //遍历 System.out.println("删除结点之后遍历链表为:"); SingleLinkedList.ListShow(); System.out.println("---------------------------------"); } }
运行结果:
循环链表
概述:单链表中最后一个结点的指针域不为空,而是指向链表的头结点,使整个链表构成一个环。
图示:
双向链表
概述:链表中有两条不同方向的链。
图示:
顺序表和链表的比较
时间性能:对于查找运算顺序表的时间复杂度为O(1),链表的时间复杂度为O(n),顺序表更好。
空间性能:事先知道数据量的大小使用顺序表更好,否则链表更好。
顺序表的存储空间利用率>链表的存储空间利用率,因为链表结点中的指针域占存储空间。