链表中数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元 素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据 域,另一个是存储下一个结点地址的指针域。
作为一个技术博主,了不起不是在创作就是在创作的路上(当然偶尔也会有点恰饭文~还指望大家多多支持),昨天的时候,了不起给大家分享了一下这个关于数据结构里面的数组是什么内容,而且也给大家说了数据结构都有什么,我们来回顾一下内容。
数据结构分类
我们在开发中,也都经常的用到数据结构,只是不是很在意这个名词,而是直接使用他们的另外的说法,比如:
上面的这四个数结构,可以统称为线性表。而除了线性表,我们还有其他的数据结构,比如散列表,树,还有图。
散列表有:
树有:
图包含:
今天我们就来分析一下这个数据结构中的另外一个,链表。
什么是链表
按照惯例,我们先从百度百科上面看看他是怎么解释的:
链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。
链表中数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元 素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据 域,另一个是存储下一个结点地址的指针域。
其实换成大白话解释,那就是一个一个的结点组合成的一个链式结构,上一个元素存储下一个元素的指针域。
我们常见的链表结构,比较少,就那么几种
单链表
单向链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节 点的指针next
实际上模拟代码:
Node{
int data;
Node next;
}
那么他的图解是什么样子呢?
图解画的比较烂,大家多见谅。
双向链表
双向链表是什么呢?
双向链表的每一个节点除了拥有data和next指针,还拥有指向前置节点的prev指针。
循环链表
那么什么是循环链表呢?
循环链表实际上就是链表的尾节点指向头节点形成一个环,称为循环链表。
那么图解是什么样的呢?
我们在昨天看数组的时候,知道了数组数组在内存中的存储方式是顺序存储(连续存储)。
那么链表呢?
其实链表在内存中的存储方式则是随机存储(链式存 储)
链表的每一个节点分布在内存的不同位置,依靠next指针关联起来。这样可以灵活有效地利用零散的碎 片空间。
我们再来一个简单的内存结构图来看一下这个链表在内存中的存储模型。
那么查找操作是什么操作呢?
查找节点:
在查找元素时,链表只能从头节点开始向后一个一个节点逐一查找。
更新节点:
找到要更新的节点,然后把旧数据替换成新数据
其实链表这个地方,很多时候后在面试的时候,经常会被问到一个内容,那就是插入还有删除,这是面试的时候经常会被问到的一个内容,那么这个插入是什么样子的呢?
链表的插入
链表的插入也即链表的构建,把点连成链。因插入位置不同分成三种情况。
在链表最前端插入数据
在链表最后插入数据
当在链表中间插入值的时候,新节点:new
原插入位置节点:temp
temp前一个节点:pre
插入操作需要做的:
new.next = temp
pre.next = new
而这个插入,又离不开 head,
我们来模拟一下这个使用 Java 代码来简单的实现一下这个头插法,尾插法,已经按照位置插入。
我们先定义一个头部
我们再建立链表对象的类:LinkNode
public class ListNode {
private int val;
private ListNode next;
public ListNode(int value) {
this.val = value;
}
public ListNode() {}
public ListNode getNext() {
return this.next;
}
public int getVal() {
return this.val;
}
public void setNext(ListNode next) {
this.next = next;
}
public void setVal(int value) {
this.val = value;
}
}
头插法
public void HeadInsert(int val) {
ListNode newNode = new ListNode(val); 首先把val建立成一个新节点
if(head == null) { 链表为空的情况
head = newNode;
return;
}
newNode.setNext(head); 链表不为空,则把原第一个节点给到新节点的next
head = newNode; 新节点成为头节点
}
尾插法
public void EndInsert(int val) {
新建节点存储数据
ListNode newNode = new ListNode(val);
判断头节点是否为空,就是链表是否为空
if(head == null) {
head = newNode;
return;
}
ListNode indexNode = head; 由于头节点head不能改变,召唤替身
while (indexNode.getNext() != null) { 从头向后遍历,直到某一节点的next
indexNode = indexNode.getNext(); 是空的,意味他是最后一个节点。
}
indexNode.setNext(newNode); 让原来最后一个节点的next指向新节点
}
按位置插入
public void Insert(int val,int index) {数值,插入位置
if(index<0 || index > this.getLength()) {
System.out.println("index位置不合法");
return;
}
if(index == 0) {
HeadInsert(val);
}else if(index == getLength()) {
EndInsert(val);
}else {
ListNode newNode = new ListNode(val);
ListNode tempNode = head;
ListNode preNode = null; pre在temp前1位
int position = 0; 通过它找到正确插入位置
while (tempNode != null) {
if(position == index) {
newNode.setNext(tempNode); pre在temp前1位
temp指向的是要被插入的位置的值
preNode.setNext(newNode);
return;
}
preNode = tempNode;
tempNode = tempNode.getNext();
position++;
}
}
}
其实相对而言,链表的删除就真的比这个插入简单了。
链表删除
public void Delete(int index) {
if (index < 0 || index>getLength()) {
System.out.println("位置不合法");
return;
}
if(index == 0) { 删除第一个节点时
head = head.getNext();
return;
}
ListNode tempNode = head;
ListNode preNode = null;
int position = 0;
while(tempNode != null) {
if(position == index) {
//pre和temp差1位,temp指向的是要被删除的值
preNode.setNext(tempNode.getNext());
//temp的下一个值由pre和temp都指向,删除temp的
tempNode.setNext(null );
return;
}
preNode = tempNode;
tempNode = tempNode.getNext();
position++;
}
}
对于这个链表,你学会了么?