抽象数据类型
- 抽象数据类型: 带有一组操作的一些对象的集合。例如,表、集合、图以及它们各自的操作一起形成的这些对象都可以被看作是抽象数据类型。
线性表定义
- 线性表: 零个或多个数据元素的有限序列
- 线性表的长度: 线性表元素的个数N。当N=0时,称为空表。
线性表的顺序存储
- 顺序实现: 可用一维数组来实现线性表的顺序存储结构
- 顺序存储结构的三个属性
- 存储空间的起始位置: 数组的存储位置就是存储空间的存储位置
- 线性表的最大存储容量: 数组长度
- 线性表的当前长度
- 数组长度和线性表长度的区别
- 数组长度: 存放线性表的存储空间的长度,存储分配后一般不改变
- 线性表长度: 线性表中元素个数的长度。随着线性表插入嗯好删除操作,该量要变化。
- 任意时刻,线性表的长度小于等于数组的长度
- 线性表顺序存储结构的优缺点
- 优点: 无须开辟额外的空间;可以快速地存取表中任意位置的元素
- 缺点: 插入和删除需要移动大量元素;当线性表长度变化较大时,难以确定存储空间的容量
线性表的链式存储
链表定义
- 链表: 包括两部分,data是存放的数据,next是指向下一个数据的指针
data是实际存储的数据值,next指针存储的是下一个数据的内存地址 - 头指针与头节点的区别
- 头指针: 链表指向第一个节点的指针,若链表有头节点,则是指向头节点的指针
- 头结点: 为了操作方便而设立的,放在第一个元素的结点之前,数据域一般无意义(可存放链表的长度),不存放具体数据。作用就是表示单链表头next
单链表基本操作
创建
思路: 首先创建结点,其后创建链表;每个结点都有自身的数据和next域表示指向下一个结点的内存地址
//1.创建结点;2.创建链表
//一个结点就是一个对象.每个结点包含实际存储的数据和指向下一个结点内存地址的next域
class HeroNode {
//自身数据
public int no;
public String name;
public String nickname;
//next指针
public HeroNode next;
//构造方法构造结点对象
public HeroNode(int hNo, String hName, Stirng hNickName) {
this.no = hNo;
this.name = hName;
this.nickname = hNickName;
}
//重写toStirng方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '}';
}
}
//创建单链表
class SingleLinkedList {
//创建头节点--头节点是不存放任何数据的,为了操作方便而设立的,放在第一个元素之前
HeroNode head = new HeroNode(0, "", "");
}
增
- 添加在链表尾部
思路: 1、因为头节点不能动,所以用一个辅助指针(变量)指向头节点;2、每添加一个结点就相当于添加到了单链表的尾部(循环遍历找链表尾部)
public void add(HeroNode heroNode) {
HeroNode temp = head;
//循环找到尾结点
while (temp.next != null) {//说明尾结点没有找到
temp = temp.next;//temp往后面移动继续找
}
//循环退出时,说明结点已经找到了,就添加
temp.next = heroNode;
}
- 添加在链表任意位置
思路: 1、循环遍历找到需要插入的结点位置;2、将新节点的next域指向插入结点的前一个结点的next域;3、将插入结点的前一个结点的next域指向新节点。例如,要在结点2和结点4中添加结点3;首先遍历循环找到结点3的位置,然后将结点4的next域指向结点3的next域;再将结点2的next域指向结点3
public void addByOrder(HeroNode newHeroNode) {
//因为头节点不能动,所以需要辅助指针(变量)指向头节点
HeroNode temp = head;
//flag表示所要插入的结点位置是否存在
boolean flag = false;
//循环遍历找所要插入结点的位置
while (true) {
if (temp.next == null) {//说明要插入的结点已经在链表最后了
break;
}
if (temp.next.no > heroNode.no) {//说明要插入的结点位置已经找到了,就是temp的后面
break;
} else if (temp.next.no == heroNode.no) {//说明要插入的结点位置已经存在
flag = true;
break;
}
//temp往后面移动继续遍历
temp = temp.next;
}
//循环退出时候,说明结点已经找到了。
if (flag) {
System.out.println("准备插入的英雄已经存在了");
} else {
heroNode.next = temp.next;
temp.next = heroNode;
}
}
删
思路: 1、找到要删除结点的前一个结点的位置;2、将要删除的前一个结点temp的next域指向temp的next域的next域
public void delete(int no) {
HeroNode temp = head;
//flag表示是否找到要删除的结点
boolean flag = false;
while (true) {
if (temp.next == null) {//已经到链表最后了
break;
}
if (temp.next.no == no) {//找到了要删除的结点位置
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.println("");
} else {
temp.next = temp.next.next;
}
}
查
改
思路: 1、根据no的值修改结点信息
//1.判断链表是否为空;2.不为空则根据no的值修改结点信息
public void update(int no) {
if (head.next = null) {
System.out.println("链表为空“);
return;
}
//因为头节点不能动,所以需要辅助指针(变量)指向头节点
HeroNode temp = head.next;
//flag表示该结点信息是否需要修改
boolean flag = false;
while (true) {
if (temp == null) {//说明到链表最后了
break;
}
if (temp.next.no = no) {//说明要修改的结点位置已经找到了
flag = true;
break;
}
//temp往后面移动继续遍历寻找
temp = temp.next;
}
//退出while循环时,判断flag,flag为真表示需要修改,为假则不需要修改
if (flag) {
temp.name = heroNode.name;
temp.nickname = heroNode.nickname;
} else {
System.out.println("结点不需要修改");
}
}
显示链表
双向链表
遍历
思路: 方式和单链表一样,只是可以向前,也可以向后查找
添加
- 添加到双向链表最后
思路: 1.先找到双向链表的最后这个结点,假设为temp;2.temp.next = newHeroNode;3.newHeroNode.pre = temp; - 插入到链表任意位置
思路:
①s.pre = p.pre;
②.p.pre.next = s;
③.s.next = p;
④.p.pre = s
修改
思路: 和单链表一样
删除
思路:
1.先找到需要删除的结点temp;
2.p.pre.next = p.next;
3.p.next.pre = p.pre