1.概述
1.1 线性表简介
线性表是具有相同特性的数据元素的一个有限系列,可以简单的理解为就是一堆数据的存放方式,是一种典型并常见的一种数据存储结构。比如说常见的英文字母表或者成绩单之类的。线性表具有以下特性:
- 有穷性: 一个线性表的元素个数是有限的。
- 一致性: 表中的所有元素的性质相同。(这里的性质相同主要是看我们从哪个角度去看待,常见的比如说元素具有相同的数据类型)
- 系列性: 表中的所有元素之间的相对位置是线性的,就是说存在唯一的开始元素和终端元素,且每个元素具有唯一的前区元素和后继元素(第一个和最后一个除外)
线性表的存储结构普遍分为顺序表和链表,两者的主要区别在于:顺序表在内存中的存储地址是顺序的,就是说地址是一个接一个的,比如:101,102,103。而链表在内存中的存储地址是顺序,不相连的,比如:101,103,102,他们主要是通过指针的方式连接在一起的。
1.2 顺序表简述
顺序表是线性表的顺序存储方式,主要是将线性表里的元素按照需对应的逻辑顺序存储在一块连续的存储空间中,其特点是存储地址有序和在建立时需注意顺序表的长度问题。如下所示,在使用顺序表时,我们要注意地址的有序性(主要以地址的方式来存储)和这个顺序表的大小(长度应为5)
常见的实现方式是使用数组来实现,数组的类型就是线性表的数据类型,数组的大小就是线性表的长度,但是需要注意的是,数组的下标是从0开始算的,而线性表的下标是从1开始算的 ,其他的相关操作和数组的操作类似,但这并不代表所有的数组都可以当做是一个线形表。
1.3 什么是链表
链表是线性表的链式存储结构,在链表中,主要是以结点和指针的方式来进行存储的,每个存储结点包含数据域(存储元素本身的信息)和一个指针域(存储下一个地址信息),而且链表在内存中的各个节点是不相连的,就是说就算存储地址相同,但有指针域的存在,元素之间的连续性并不是按照地址上来的。
链表与顺序表的区别在于:由于顺序表的存储时有序的,在查找数据上会比较方便,以数组为例子,由于有数组下标等因素的存在,我们在查找因素会很快,当如果我们想要添加元素等操作时,涉及到元素移位和数组扩容等问题,效率会很慢。相反,链表在添加元素方面效率相对来说很快,而在查找因素上就相对来说慢了。
链表又分为单链表,双链表,循环链表等,每一种链表都不同。主要是看存储结点的前继结点和后继结点的数量和连线方式。
2.单链表
2.1简介
在单链表的设计中,主要是在结点上设置一个指针域,由指针指向后继结点(下一个元素),以此类推,将数据一个一个链起来,这就是单链表。单链表有的头指针指向头结点,头结点的数据域一般设为0,从1开始才设置数据域,所以链表的起点是1,尾结点的指针域为空值,因为没有后继元素相连了。
单链表又分为带头结点和不带头结点两种,不管带不带头结点,头指针都指向第一个结点(有头结点就指向头结点),头结点的设置上,数据域可以为空,但指针域必须指向接下来的第一个结点。
2.2 结点的插入
//c语言写法:
s->next = p->next;
p->next = s;
单链表结点的插入如上所示,首先让a结点(新结点)的指针域(s->next)指向a1结点(p->next(因为在链表中,头结点0的next指针域已经指向了a1结点,所以p->next代表了a1的整个结点)),然后让0(头结点)的指针域指向a结点(s),从而实现结点的插入操作。
注意: 不能先执行p->next = s;然后再执行s->next = p->next;,这样子相当于指向a1结点的指针给弄没了,相当于执行了s->next=s。(链表的结点插入操作最重要的就是不断链,通常情况下都是断开后链再断前链)
//Java写法:(这里的head指头结点,temp指临时变量,s指新结点)
temp = head;
s.next = temp.next;
temp.next = s;
在Java中,因为没有指针的存在,我们可以使用一个临时变量和循环的方式来找到新结点的插入位置,这个临时变量普遍都是用来替代头结点或者第一个结点,可以理解为头结点的一个副本,找到要插入的位置后,首先让新结点的next域等于临时变量的next域,然后再把新结点等于临时变量的next域。(Java操作和C语言操作的思路是一样的,只不过是用临时变量来替代指针而已)
2.3 结点的删除
//C语言写法:先临时保存被删结点,然后删除q,最后释放内存空间
q=p->next;
p->next=q->next;
free(q);
在单链表中,如果想要删除一个结点,只需将该结点的前继结点的next域指向该结点的后继结点即可,如上图所示:将头结点的next指向a2即可。
//Java写法
temp.next=temp.next.next;
在Java中,删除一个结点只需将链表中的该结点的下一个结点赋值给该结点的上一个结点即可,因为有GC回收的存在,该结点会被GC回收掉。
2.4 单链表的创建
单链表的创建方式有两种,分别是头插法和尾插法,两种之间的区别在于:头插法只要是将节点一个个连接在头结点之后,而尾插法则是将节点链接在最后一个结点上,简单来说尾插法就是新增一个尾结点(并不是说这就代表一个链表有两个尾结点,而是将尾结点后移一位而已)。
1.头插法: 头插法主要是将结点(数据存放到结点,比如下面的a1)插入链表的表头上,就是说插入到头结点之后。
//C语言写法
s->next = p->next;
p->next = s;
//头插法:其中node表示结点信息,里面包含数据源的变量和一个next变量
public void CreateInFirst(Node node) {
//定义辅助变量
Node temp = head;
node.next = temp.next;
temp.next = node;
}
2.尾插法: 尾插法主要是将结点插入到链表的后面,在C语言中,可以定义一个尾指针指向最后那个结点,然后将尾结点的next域设置为空。而在Java中,我们只需要判断结点的next是否为空,然后将结点添加进去即可。
//C语言写法
r->next = s;
r=s;
public void CreateInTail(Node node){
Node temp = head;
while (true){
if(temp.next == null){
break;
}
//当没有找到,temp后移
temp = temp.next;
}
//当跳出循环时,temp已经指向最后
temp.next = node;
}
2.5 相关的CRUD操作
和其他数据结构一样,链表也有其相关的操作,如常见的增删改查,链表反转,查找指定元素等等,以下便是链表常见的crud的一个操作。
package Juc;
public class test01 {
public static void main(String[] args) {
OperateLinkedList linkedList = new OperateLinkedList();
//定义结点信息
Node node1 = new Node("1", "张三");
Node node2 = new Node("2", "李四");
Node node3 = new Node("3", "赵五");
//头插法
// linkedList.CreateInFirst(node1);
// linkedList.CreateInFirst(node2);
// linkedList.CreateInFirst(node3);
//尾插法
System.out.print("尾插法创建:");
linkedList.CreateInTail(node1);
linkedList.CreateInTail(node2);
linkedList.CreateInTail(node3);
linkedList.list();
//查询结点位置
linkedList.Query(node2);
//修改结点
Node node4 = new Node("2", "哈哈哈");
linkedList.Update(node4);
//删除结点
linkedList.Del(node4);
}
}
//结点信息
class Node {
public String id;
public String name;
public Node next;
public Node(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
"}";
}
}
class OperateLinkedList {
//声明头结点:头结点不能动,可以始于一个辅助变量来操作
Node head = new Node("", "");
//头插法
public void CreateInFirst(Node node) {
//定义辅助变量
Node temp = head;
node.next = temp.next;
temp.next = node;
}
//尾插法
public void CreateInTail(Node node) {
Node temp = head;
while (true) {
if (temp.next == null) {
break;
}
//当没有找到,temp后移
temp = temp.next;
}
//当跳出循环时,temp已经指向最后
temp.next = node;
}
//查找结点在链表中的位置
public void Query(Node node) {
Node temp = head;
int i = 0;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id && temp.name == node.name) {
flag = true;
break;
}
//辅助变量向后移位
temp = temp.next;
i++;
}
if (flag) {
System.out.println("当前结点在链表中处于第" + i + "位");
} else {
System.out.println("该结点不存在");
}
}
//修改结点指定元素,根据ID修改
public void Update(Node node) {
Node temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = node.name;
System.out.print("修改成功,链表数据为:");
list();
} else {
System.out.println("该元素不存在");
}
}
//删除结点
public void Del(Node node) {
Node temp = head;