PS:本文将介绍什么是链表,以及单链表、双向链表、循环链表的认识和代码实现
什么是链表?
链表是一种常见的数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必按顺序存储,链表在插入的时候可以达到O⑴的复杂度,比顺序表快很多,但是查询一个节点或访问特定下标的节点则需要O(n)的时间,而顺序表相应的时间复杂度为 O(1) 和 O(logn) 。
链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点地址的链接(links),链表中每个数据都有一个指针,用来指向下一个数据的内存地址,如图:
在链表中,数据一般是分散存储在内存中的,无须连续存储在内存,所以想要访问数据的话需要从第一个数据开始访问,顺着指针的地址指向来进行逐一访问。使用链表结构可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
链表的特点:
链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素 与其直接后继数据元素 之间的逻辑关系,对数据元素来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的内存地址)。
┌───┬───┐ data域 -- 存放结点值的数据域
│data │next │ next域 -- 存放结点的直接后继的地址的指针域(链域)
└───┴───┘
单链表
单链表是一种简单切常用的链式表,每个节点只有一个next域指向
单链表分为了两种:带头节点的单链表和不带头节点的单链表
不带头节点的单链表,链表的第一个节点就存放元素,指针指向链表的第一个节点
带头节点的单链表:头节点的data为空,不保存信息
不带头节点的单链表
单链表随机插入以及删除节点实际是此节点的地址与后继节点地址的改变,如图:
1.定义pre指向第一个节点,定义cur指向第一个节点的下一个节点,则cur.next为 cur的后继节点
2.在指定位置插入时,让新节点的地址指向cur,将pre的地址指向新插入的节点,如图:
3.在指定位置删除时,通过while()遍历,将待删除节点下标的元素作为cur,将pre的地址指向cur的后继节点,即可删除此元素
4.插入和删除时都要判断是否是操作的第一个节点 (头插不用判断)
/**
* 不带头节点的单链表实现
* @param <T>
*/
class Link<T>{
// 指向单链表的第一个节点
private Entry<T> head;
//节点的位置
private int post = 0;
public Link(){
this.head = null;
}
/**
* 单链表的节点类型
* @param <T>
*/
private class Entry<T>{
T data;// 链表节点的数据域
Entry<T> next; // 下一个节点的引用
public Entry(T data, Entry<T> next) {
this.data = data;
this.next = next;
}
}
/**
* 单链表的头插法
* @param data
*/
public void insertHead(T data){
Entry<T> newNode = new Entry<> (data,head);
head = newNode;
}
/**
* 单链表的尾插法
* @param data
*/
public void insertTail(T data){
Entry<T> newNode = new Entry<> (data,null);
if(head == null){
head = newNode;
return;
}
Entry<T> cur = head;
//cur的地址指向为null,证明为最后一个节点,可进行尾插操作
while (cur.next != null){
cur = cur.next;
}
cur.next = newNode;
}
/**
* 在任意位置插入节点 在下标为index的位置插入元素
* @param index
* @param data
*/
public void insertArb(int index, T data){
Entry<T> newNode = new Entry<> (data,null);
Entry<T> pre = null;
Entry<T> cur = head;
while (post != index){
pre = cur;
cur = cur.next;
post++;
}
if(pre == null){
head = head.next;
}else{
newNode.next = cur;
pre.next =newNode;
post = 0;
}
}
/**
* 根据节点的值删除节点(注意:如果出现两个data相同的值只能删除第一个)
* @param data
*/
public void removeData(T data){
Entry<T> pre = null;
Entry<T> cur = head;
while (cur.data != data){
pre = cur;
cur = cur.next;
}
if(pre == null){
//删除的是第一个节点
head = head.next;
}else {
//删除的是第一个节点之外的其他节点
pre.next = cur.next;
}
}
/**
* 删除任意位置的节点
* @param index
*/
public void removeArb(int index){
Entry<T> pre = null;
Entry<T> cur = head;
while (post != index){
post++;
pre = cur;
cur = cur.next;
}
if(pre == null){
head = head.next;
}else {
post = 0;
pre.next = cur.next;
}
}
/**
* 返回链表长度
* @return
*/
public int getLength(){
Entry<T> node = this.head;
int length = 0;
while (node != null){
length++;
node = node.next;
}
return length;
}
/**
* 打印单链表的所有元素
*/
public void show(){
Entry<T> node = this.head;
while (node != null){
System.out.print (node.data + " ");
node = node.next;
}
System.out.println ();
}
// 根据数据查找节点信息
public Entry<T> showData(int index){
Entry<T> cur = head;
while (post != index){
cur = cur.next;
post++;
}
return cur;
}
// 根据位置查找节点信息
public Entry<T> showNext(T data){
Entry<T> cur = head;
while (cur.data != data){
if(cur.next == null){
return null;
}else {
cur = cur.next;
}
}
return cur;
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Link<Integer> link = new Link<> ();
link.insertHead (10);
link.insertHead (20);
link.insertHead (30);
System.out.print ("头插法:");
link.show();
link.insertTail (10);
link.insertTail (20);
link.insertTail (30);
System.out.print("头插加尾插:");
link.show ();
link.insertArb (3,35);
System.out.print("在下标为3的位置插入元素35:");
link.show ();
link.removeData (20);
System.out.print ("删除元素20: ");
link.show ();
link.removeArb (4);
System.out.print ("删除下标为4的元素: ");
link.show ();
System.out.println ("链表节点个数为: " + link.getLength ());
}
}
打印结果:
头插法:30 20 10
头插加尾插:30 20 10 10 20 30
在下标为3的位置插入元素35:30 20 10 35 10 20 30
删除元素20: 30 10 35 10 20 30
删除下标为4的元素: 30 10 35 10 30
链表节点个数为: 5
带头节点的单链表实现
带头节点与不带头节点的区别在于,带头节点定义了第一个节点为空,头插时在head.next的位置进行新节点的插入,以及在进行单链表操作时都需要考虑到头结点的存在,与不带头节点的操作基本一致
注:当时在写带头单链表时,考虑到是否将为空的头结点算入链表长度中,通过对单链表的进一步认识,理解了带头的单链表只是单链表的另一种表现,头节点不会计入节点长度和影响到节点的下标(最终链表中有效节点个数就是链表长度,将head.next的节点下标定义为0)
package 链表.单链表;
class LInkHead<T>{
// 指向单链表的第一个节点
public Entry<T> head;
private int pos = 0;
public LInkHead(){
this.head = new Entry<>(null,null);
}
/**
* 节点的类型
* @param <T>
*/
class Entry<T> {
T data;// 链表节点的数据域
Entry<T> next; // 下一个节点的引用
public Entry(T data, Entry<T> next){
this.data = data;
this.next = next;
}
}
/**
* 头插法
* @param data
*/
public Entry<T> insertHead(T data){
Entry<T> node = new Entry<> (data,head.next);
head.next = node;
return node;
}
/**
* 尾插法
* @param data
*/
public void insertTail(T data){
Entry<T> node = head;
while (node.next != null){
node = node.next;
}
node.next = new Entry<> (data,null);
}
/**
* 在下标为index的位置插入元素
* @param index
* @param data
*/
public void insertArb(int index,T data){
Entry<T> node = new Entry<> (data,head.next);
Entry<T> pre = head;
Entry<T> cur = head.next;
while (pos != index){
pos++;
pre = cur;
cur = cur.next;
}
pre.next = node;
node.next = cur;
pos = 0;
}
/**
* 删除所有值为data的节点
* @param data
*/
public void removeTail(T data){
Entry<T> pre = head;
Entry<T> cur = head.next;
while (cur != null){
if(cur.data == data){
// data节点的删除
pre.next = cur.next;
//break; //只删除第一个值为data的节点(与下面的删除功能相同)
cur = pre.next; // 删除链表中所有值为val的节点
}else {
pre = cur;
cur = cur.next;
}
}
}
/**
* 删除给定元素的值(只能删除第一个出现的尾data的值)
* @param data
*/
public void removeData(T data){
Entry<T> pre = head;
Entry<T> cur = head.next;
while (cur.data != data){
pre = cur;
cur = cur.next;
}
if(pre.next == null){
head = head.next;
}else {
pre.next = cur.next;
}
}
/**
* 删除指定下标的元素
* @param index
*/
public void removeArb(int index){
Entry<T> pre = head;
Entry<T> cur = head.next;
while (pos != index){
pre = cur;
cur = cur.next;
pos++;
}
if(pre.next == null){
head = head.next;
}else {
pre.next = cur.next;
pos = 0;
}
}
/**
* 获取链表长度
* @return
*/
public int getLength(){
Entry<T> cur = head.next;
int count = 0;
while (cur != null){
count++;
cur = cur.next;
}
return count;
}
/**
* 顺序打印单链表
*/
public void show(){
Entry<T> cur = head.next;
while (cur != null){
System.out.print (cur.data + " ");
cur = cur.next;
}
System.out.println ();
}
/**
* 逆序打印单链表
*/
public void show2(Entry<T> head){
Entry<T> cur = head.next;
if (cur != null){
show2 (cur);
System.out.print (cur.data + " ");
}
}
}
带头节点的循环链表
循环链表是另一种形式的链式存储结构。它的特点是表中最后一个结点的地址域指向头节点,即遍历链表的循环条件是 while(cur != head){} ,整个链表形成一个环
/**
* 带头节点的循环链表
* @param <T>
*/
class CircleLink<T> {
public Entry<T> head;
private int pos = 0;
//头结点的初始化
public CircleLink() {
this.head = new Entry<> (null, null);
this.head.next = head;
}
public class Entry<T> {
T data;
Entry<T> next;
public Entry(T data, Entry<T> next) {
this.data = data;
this.next = next;
}
}
/**
* 头插法
* @param data
*/
public void insertHead(T data) {
Entry<T> node = new Entry<> (data, head.next);
head.next = node;
}
/**
* 尾插法
* @param data
*/
public void insertTail(T data) {
Entry<T> node = new Entry<> (data, head);
Entry<T> cur = head;
//单链表尾部最后一个节点指向头结点
while (cur.next != head) {
cur = cur.next;
}
cur.next = node;
}
/**
* 在下标为index的位置插入节点
* @param index
* @param data
*/
public void insertArb(int index, T data) {
Entry<T> node = new Entry<> (data, head);
Entry<T> pre = head;
Entry<T> cur = head.next;
while (pos != index && cur != head) {
pos++;
pre = cur;
cur = cur.next;
}
pre.next = node;
node.next = cur;
pos = 0;
}
/**
* 删除第一个值为data节点 或 删除所有值为data的节点
* @param data
*/
public void removeAllData(T data) {
Entry<T> pre = head;
Entry<T> cur = head.next;
while (cur != head) {
if (cur.data == data) {
pre.next = cur.next;
//break;//只删除第一个值为data的节点
cur = pre.next;// 删除链表中所有值为data的节点
} else {
pre = cur;
cur = cur.next;
}
}
}
/**
* 删除下标为index的节点
* @param index
*/
public void removeArb(int index) {
Entry<T> pre = head;
Entry<T> cur = head.next;
while (pos != index && cur != head){
pre = cur;
cur = cur.next;
pos++;
}
pre.next = cur.next;
pos = 0;
}
/**
* 获取链表长度
* @return
*/
public int getLength(){
Entry<T> cur = head.next;
int count= 0;
while (cur != head){
count++;
cur = cur.next;
}
return count;
}
/**
* 打印链表所有节点值
*/
public void show() {
Entry<T> cur = head.next;
while (cur != head) {
System.out.print (cur.data + " ");
cur = cur.next;
}
System.out.println ();
}
}
带头节点的双向链表
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个地址指向(前驱 pre 后继 next),分别指向直接后继和直接前驱。cur为一个节点,cur.pre则表示cur的前驱节点,cur.next表示cur的后继节点,表示起来更加方便
class DoubleLink<T>{
Entry<T> head;
private int pos = 0;
public DoubleLink(){
this.head = new Entry<T>(null,null,null);
}
/**
* 定义结点类型
* @param <T>
*/
public class Entry<T>{
T data;
Entry<T> pre; // 存储前一个节点的地址
Entry<T> next; // 存储后一个节点的地址
public Entry(T data, Entry<T> pre, Entry<T> next) {
this.data = data;
this.pre = pre;
this.next = next;
}
}
/**
* 头插
* @param data
*/
public void insertHead(T data){
Entry<T> node = new Entry<> (data,head.next,head.next);
head.next = node;
}
/**
* 尾插
* @param data
*/
public void insertTail(T data){
Entry<T> node = new Entry<> (data,null,null);
Entry<T> cur = head;
while (cur.next != null){
cur = cur.next;
}
node.pre = cur;
cur.next = node;
}
/**
* 下下标为index的位置插入值为data的节点
* @param index
* @param data
*/
public void insertArb(int index,T data){
Entry<T> node = new Entry<> (data,null,null);
Entry<T> pre = head;
Entry<T> cur = head.next;
while (pos != index){
pos++;
pre = cur;
cur = cur.next;
}
pre.next = node;
node.pre = pre;
node.next = cur;
//当cur不为空时,cur才有前驱地址指向node
if(cur != null) {
cur.pre = node;
}
pos = 0;
}
public void removeArb(T data){
Entry<T> cur = head.next;
while (cur != null) {
//如果cur的值=data,cur前驱节点指向的地址为cur后继节点
if (cur.data == data) {
cur.pre.next = cur.next;
//如果cur的后继节点不为空,cur后继节点指向的地址为cur的前驱节点
if(cur.next != null){
cur.next.pre = cur.pre;
}
// break; //加上break只删除第一个出现的值为data的节点
}
cur = cur.next;
}
}
/**
* 删除值为data的节点(默认删除第一个)
* @param data
*/
public void removeData(T data){
Entry<T> cur = head.next;
while (cur.data != data){
cur = cur.next;
}
cur.pre.next = cur.next;
if(cur.next != null){
cur.next.pre = cur.pre;
}
}
/**
* 打印单链表所有节点的值
*/
public void show(){
Entry<T> cur = head.next;
while (cur != null){
System.out.print (cur.data + " ");
cur = cur.next;
}
System.out.println ();
}
/**
* 逆序打印单链表
* @param head
*/
public void show2(Entry<T> head){
Entry<T> cur = head.next;
if(cur != null){
show2 (cur);
System.out.print (cur.data + " ");
cur = cur.next;
}
}
}