-
什么是链表
概念
链表
是一种物理存储单元上非连续/非顺序的存储结构.
-
节点
- 概念
- 所谓链表即是由多个节点组成,像一条铁链,铁链的每一个环就相当于链表的节点.
- 组成
- 数据域
- 一个节点用于储存数据的内存控件
- 指针域
- 一个节点用于储存和它连接的下一个节点地址的内存空间
-
优点
- 创建节点
- 不需要指定内存空间的大小,非常具有灵活性
- 删除节点
- 删除一个节点不需要像数组那样将其余的数据整体向前移动,只需要修改节点的指针域即可完成删除节点的操作
-
缺点
- 访问节点
- 可以通过循环或者递归访问到链表的任何数据,但是访问效率低于数组(线性数据结构)
- 储存方面
- 由于不是线性结构,并且相对于线性的储存结构来说,需要保存每个节点的指针域,这又是一笔内存开销,占用了内存空间,对内存空间的利用效率低
-
链表的种类
-
方向
- 单向链表
- 是连接方向单一的链表,从头结点开始可以一直指向尾节点,方向不会改变(即从头结点走到尾节点只有一条路径).其中每一个节点的指针域只有一个指向,也就是一个节点有一个指针,可以保存下一个节点
-
双向链表
- 每一个节点的指针域中保存有两个指针(引用),可以同时指向该节点的前驱节点和后继节点
-
形状
-
单链表
- 单链表:是一种逻辑上的线性结构,只有一条链,首尾不相接
-
环形链表
- 环形链表:首尾相接的链表(类比丢手帕)(约瑟夫问题)
-
如何创建链表
-
思路分析
- 图形化结构
-
链表
- 节点
-
节点类图
- 节点属性
- 数据域 : 节点中储存数据内存控件
- 指针域 : 节点中储存其他节点(可能是下一个,也可能是上一个)的地址信息的内存空间
-
节点类图
-
链表
- 图形化结构
-
类图
-
[Java]单项链表类图//TODO 修改类图(无bug的类图)
- 文本思路
- 单向链表
* //TODO 书写思路 - 双向链表
* //TODO 书写思路 - 单向环形链表
* //TODO 书写思路 - 双向环形链表
* //TODO 书写思路
- 单向链表
- 文本思路
-
[Java]单项链表类图//TODO 修改类图(无bug的类图)
代码实现
[Java]单向链表
[Java]双向链表//TODO 添加超链接
[Java]单向环形链表//TODO 添加超链接
[Java]双向环形链表//TODO 添加超链接
-
-
如何使用链表
-
节点
-
属性
- 数据域 : Object data
- 指针域 :
- Node successorNode//前驱节点(双向链表节点特有属性)
- Node precursorNode//后继节点
-
方法
- 构造方法 : (根据传入的Object对象创建一个后继节点指向null的Node对象)
- 查询节点数据 : private Object getData()
- 修改节点数据 : private void setData(Object newData)//当Node类的成员属性不是私有的时候不需要这些方法,并且就算有这些方法,它们的修饰符也不能是private
-
链表
属性
长度 : int length = 0
头结点引用 : Node headNode = null
尾节点引用 : Node tailNode = null ? ?(双向链表特有属性)
方法[单向链表]//TODO完成四种链表的分类
初始化链表(利用构造器) : public LinkedList()
增加尾节点 : public void add()
插入指定节点: public void insert(int index)
删除尾节点 : public void del()
删除指定节点 : public void del(int index)
修改尾节点 : public void update(Object newData)
修改指定节点 : public void update(int index, Object newData)
获取尾节点 : public Node getNode()
获取指定节点 : public Node getNode(int index)
链表长度 : public int length()
判断空链表 : public boolean isEmpty()
排序 : public void sort()
截取链表 : public LinkedList subLinkedList(int start, int end)
-
-
如何进行测试
-
当我们构建好了我们自己的链表的时候,就必须得对其正确性和健壮性进行测试,这个测试的过程一般来说是随着编码一起进行的,我们来看一下应该如何进行测试.
- 正确性//TODO
- 健壮性//TODO
-
-
如何优化链表
- 算法方面
- 排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 检索算法
- 操作方法
- 健壮性
- 待续
- 算法方面
-
链表的应用场景
-
首先我们知道链表的特点就是可以动态地增加和删除元素,就类似与一个可以自动扩大和缩小容量的数组,因此,我们可以想到它的应用场景应该有:
- 1.求解约瑟夫问题(利用环形链表)
-
代码实现
[Java]单向链表代码 :
package linkedList;
/**
* 节点类(单向链表)
* @author 王一航
*/
class Node {
//数据域 Object data;
//指针域 Node nextNode = null;//单向链表因此只有一个指向自身类型的引用
//构造函数
public Node(Object data){
this.data = data;
}
}
package linkedList;
/** * 单向链表类 * @author 王一航 */
public class LinkedList {
/** * 成员变量 */ int length = 0;
Node headNode = null;
/** * 成员方法 */
//初始化链表(利用构造器)
public LinkedList(){
int length = 0;//记录链表长度
Node headNode = null;//头结点
}
//增加尾节点
public void add(Object data){
insert(length, data);
}
//插入指定节点
public void insert(int index, Object data){
Node temp = getNode(index);//使用一个临时的引用记录需要插入的位置的前驱结点的地址
Node node = new Node(data);//创建新节点准备插入
if(index > 0){
getNode(index - 1).nextNode = node;
}else{
headNode = node;
}
node.nextNode = temp;
length++;
}
//删除尾节点
public void del(){
del(length - 1);
}
//删除指定节点
public void del(int index){
//TODO 检查入口参数
if(index > 0){
getNode(index - 1).nextNode = getNode(index + 1);
length--;
}
if(index == 0){
headNode = getNode(1);
length--;
} //TODO 释放内存
}
//修改尾节点
public void update(Object newData){
update(length - 1, newData);
}
//修改指定节点
public void update(int index, Object newData){
getNode(index).data = newData;
}
//获取尾节点
public Node getNode(){
return getNode(length - 1);
}
//获取指定节点
public Node getNode(int index){
try{
point = headNode;
int count = 0;
while(count < index){
point = point.nextNode;
count++;
}
return point;
}catch(NullPointerException e){
System.out.println("请检查链表是否越界! 链表长度 : " + length);
return null;
}
}
//链表长度
public int length(){
return length;
}
//判断空链表
public boolean isEmpty(){
return length == 0 ? true : false;
}
//排序
public void sort(){
//TODO 完成排序功能
System.out.println("请完成排序功能!");
}
//截取链表
public LinkedList subLinkedList(int start, int end){
if(end >= start){
headNode = getNode(start);
getNode(end - start).nextNode = null;
length = end - start;
}
return this;
//TODO 释放不需要的内存
}
//打印链表所有元素
public void show(){
for(int i = 0; i < length; i++){
System.out.println("索引 : " + i + " 第" + (i+1) + "个" + getNode(i).data);
}
}
}
[Java]双向链表代码 :
package doubleLinkedList;
/** * 双向链表节点类 * @author 王一航 */
class Node {
//数据域 Object data;
//指针域 Node lastNode = null;//前驱节点引用
Node nextNode = null;//后继节点引用
//构造函数 public Node(Object data){
this.data = data;
}
}
//TODO 添加双向链表的代码
[Java]单向环形链表代码 :
//TODO 添加代码
package circularLinkedList;
/** * 单向环形链表节点类 * @author 王一航 */
public class Node {
/** * 成员属性 */
//数据域 Object data;
//指针域 Node successorNode;//后继节点
/** * 成员方法 */
public Node(Object data){
this.data = data;
}
}
[Java]双向环形链表代码 :
//TODO 添加代码
package circularLinkedList;
/** * 双向环形链表节点类 * @author 王一航 */
public class Node {
/** * 成员属性 */
//数据域 Object data;
//指针域 Node successorNode;//后继节点
Node precursorNode;//前驱节点
/** * 成员方法 */
public Node(Object data){
this.data = data;
}
}
总结(个人理解):
- 关于健壮性 :
当某一个方法需要入口参数的时候,必须对入口参数的合法性进行检查(以后要将这种意识变成一种意识) - 关于代码结构 :
- 关于方法的组织 :
良好的代码中方法的组织结构应该具有"高内聚低耦合"的特点
高内聚 :
低耦合 : - 关于属性和方法的命名 :
- 关于类图的使用 :
注意 :
1 . 杜绝魔术数字的出现,程序中一旦需要使用到某些常量,则必须首先声明常量为一个有意义的名字,然后根据这个名字去访问该常量