数据结构--双链表的设计与实现
首先了解其定义:
双链表:每个结点中设置两个指针成员,分别用于指向前驱结点和后继结点,这样的链表简称双向链表。与单链表类似不过多了一个指针成员。 需要了解单链表与指针成员,可参考浏览我的
上一期博客,其中对单链表进行了完整的解剖。
了解了双链表的概念,接下来见识它的模型:
双链表表与单链表一样,同样有头结点;头结点不存放data数据。但各结点之间为双向指向。
相应图解为:
这里为什么要画椭圆,仅根据我个人的理解,在单链表一期中已有介绍。这里做简单描述:1.各实例化对象默认拥有对应构造方法内的所有内容;2.当你往构造方法内加内容时,对应实例化对象所拥有的内容也会增多,所以我这里采用椭圆来理解各结点。
了解了双链表的结构,那么如何设计建立双链表呢?与单链表相同,有两种方法:
一.头插法建立双链表表:这里我画了一张图来理解
同样,了解了原理设计代码:
//用头插法建立双链表
public void CreateHDList(Object[] a){
DoubleList1<Object> s;
for(int i=0;i<a.length;i++){
s = new DoubleList1<>(a[i]);
s.next = head.next;
if(head.next!=null)
head.next.prior = s;
head.next = s;
s.prior = head;
}
}
为什么这里要s.next = head.next;(为了保证插入的结点之间为双向指向),当插入第一个结点时这个语句本来是没有必要的,因为在构造结点时,s.next本来就是指向为空的。但是当继续插入结点时,就必须添加这条语句了,因为下一个结点插入时,需要该语句改变其引用,若没有该语句,则下一个结点s1与结点s达不到双向指向,读者可自行进行推导验算。这里无需置最后一个结点的next指向null,因为它本来就是指向null的。
二.尾插法建立双链表表:同样先附图理解
与头插法不同的是,在进行建表的过程中,中间结点t的指向是变化的。t是一个中间变量,在一个数据插入之后,t指向下一个结点;
第一种情况:当所有元素插入完之后,需将双链表收尾。所以将t置空,没有元素要插入了,t结点也就没有用了,将其置空释放内存空间。
第二种情况:若最后一个结点在插入前其next已有指向,但我们并不想让其进入该双链表,所以将其置空。
了解了建表方法,接下来正式进入双链表的功能设计阶段
1.创建一个DoubleList1类:构造结点内部结构
相关代码为:
//创建DoubleList类,生产结点
class DoubleList1<Object>{
Object data;
DoubleList1<Object> prior;
DoubleList1<Object> next;
//创建头结点结构
public DoubleList1(){
prior = null;
next = null;
}
//创建普通结点结构
public DoubleList1(Object d){
data = d;
prior = null;
next = null;
}
}
代码的具体用法在注释中已经说明。
2.创建一个DoubleList2类:创建双链表以及实现双链表的增删改查
相关代码为:以下方法均在此类中
2.1:创建一个头结点
//创建头结点
DoubleList1<Object> head = new DoubleList1<>();
2.2:头插法建表
//用头插法建立双链表
public void CreateHDList(Object[] a){
DoubleList1<Object> s;
for(int i=0;i<a.length;i++){
s = new DoubleList1<>(a[i]);
s.next = head.next;
if(head.next!=null)
head.next.prior = s;
head.next = s;
s.prior = head;
}
}
2.3:尾插法建表
//用尾插法建立双链表
public void CreatLDList(Object[] a){
DoubleList1<Object> s;
DoubleList1<Object> t;
t = head;
for (int i = 0; i < a.length; i++) {
s = new DoubleList1<Object>(a[i]);
t.next = s;
s.prior = t;
t = s;
}
t.next = null;
}
尾插法和头插法上面已经进行讲解,这里不再赘述。
在实现增删改查前:
2.4:获取双链表的长度
//在实现双链表增删改查之前,先求出双链表的长度
//也可使用前驱结点prior进行求size,这里我们采用后驱结点next进行求size
public int size(){
DoubleList1<Object> p = head;
int count = 0;
while(p.next!=null){
count++;
p = p.next;
}
return count;
}
从这里可以大致看出,双链表的头结点索引为-1,size为0;头结点下一结点索引为0,size为1。
2.5:获取双链表中某个位置对应的的结点
//在实现双链表增删改查之前,先求出双链表某个元素的对应结点
//也可使用前驱结点prior进行求位置结点,这里我们采用后驱结点next进行求位置结点
public DoubleList1<Object> geti(int i){
DoubleList1<Object> p = head;
int j = -1; //这里设计了一个细节,默认设置双链表索引从-1开始,即头结点索引为-1,且头结点不存放数据
while(j<i){
j++;
p = p.next;
}
return p;
}
从这里发现,我们自己定义了双链表的索引。头结点head索引为-1,存放数据的第一个结点s索引为0。这与数组类似,首元素下标为0。双链表同样如此,即首个存放数据的结点索引为0。(这是由我们自己定义的)。
2.6:实现双链表的增操作
//实现元素的增操作,实现在指定位置插入,若直接插入,调用上面两个方法即可
public void Insert(int i,Object e){
if(i<0||i>size()){
throw new IllegalArgumentException("位置i不在有效范围之内!");
}
DoubleList1<Object> s = new DoubleList1<>(e);
DoubleList1<Object> p = geti(i-1);
s.next = p.next; //在其后插入
if(p.next!=null){ //若后面结点不为空,则双向指向
p.next.prior = s;
}
p.next = s;
s.prior = p;
}
2.7:实现双链表的删操作
//实现元素的删操作,删除指定位置元素
public void Delete(int i){
if(i<0||i>size()-1){
throw new IllegalArgumentException("位置i不在有效范围之内!");
}
DoubleList1<Object> p = geti(i);
if(p.next!=null){
p.next.prior = p.prior;
}
p.prior.next = p.next;
}
2.8:实现双链表的改操作
//实现元素的改操作,修改指定位置的元素
public void SetElem(int i,Object e){
if(i<0||i>size()-1){
throw new IllegalArgumentException("位置i不在有效范围之内!");
}
DoubleList1<Object> p = geti(i);
p.data = e;
}
2.9:实现双链表的查操作
2.9.1:查找指定元素并返回
//实现元素的查操作,查找指定位置的元素
public Object getElem(int i){
DoubleList1<Object> p = head;
int j = -1;
while(j<i){
j++;
p = p.next;
}
return p.data;
}
2.9.1:查找双链表全部元素并返回
//将双链表中全部元素转换为字符串并返回
public String ToString(){
String ans = "";
DoubleList1<Object> p = head.next;
while(p!=null){
ans+=p.data+" ";
p=p.next;
}
return ans;
}
3.创建一个DoubleList主类:实现结构与表现相分离
相关代码为:(功能测试)
public class DoubleList {
public static void main(String[] args) {
Integer[] a = {1,2,3,4,5};
DoubleList2<Integer> doubleList2 = new DoubleList2<>();
doubleList2.CreateHDList(a);
System.out.println(doubleList2.getElem(3));
System.out.println(doubleList2.ToString());
DoubleList2<Integer> doubleList3 = new DoubleList2<>();
doubleList3.CreatLDList(a);
System.out.println(doubleList3.ToString());
}
}
所有代码为:
//创建DoubleList类,生产结点
class DoubleList1<Object>{
Object data;
DoubleList1<Object> prior;
DoubleList1<Object> next;
//创建头结点结构
public DoubleList1(){
prior = null;
next = null;
}
//创建普通结点结构
public DoubleList1(Object d){
data = d;
prior = null;
next = null;
}
}
//利用DoubleList1创建对象,创造链表结构,实现链表基本功能
class DoubleList2<Object>{
//创建头结点
DoubleList1<Object> head = new DoubleList1<>();
//用头插法建立双链表
public void CreateHDList(Object[] a){
DoubleList1<Object> s;
for(int i=0;i<a.length;i++){
s = new DoubleList1<>(a[i]);
s.next = head.next;
if(head.next!=null)
head.next.prior = s;
head.next = s;
s.prior = head;
}
}
//用尾插法建立双链表
public void CreatLDList(Object[] a){
DoubleList1<Object> s;
DoubleList1<Object> t;
t = head;
for (int i = 0; i < a.length; i++) {
s = new DoubleList1<Object>(a[i]);
t.next = s;
s.prior = t;
t = s;
}
t.next = null;
}
//在实现双链表增删改查之前,先求出双链表的长度
//也可使用前驱结点prior进行求size,这里我们采用后驱结点next进行求size
public int size(){
DoubleList1<Object> p = head;
int count = 0;
while(p.next!=null){
count++;
p = p.next;
}
return count;
}
//在实现双链表增删改查之前,先求出双链表某个元素的对应结点
//也可使用前驱结点prior进行求位置结点,这里我们采用后驱结点next进行求位置结点
public DoubleList1<Object> geti(int i){
DoubleList1<Object> p = head;
int j = -1; //这里设计了一个细节,默认设置双链表索引从-1开始,即头结点索引为-1,且头结点不存放数据
while(j<i){
j++;
p = p.next;
}
return p;
}
//实现元素的增操作,实现在指定位置插入,若直接插入,调用上面两个方法即可
public void Insert(int i,Object e){
if(i<0||i>size()){
throw new IllegalArgumentException("位置i不在有效范围之内!");
}
DoubleList1<Object> s = new DoubleList1<>(e);
DoubleList1<Object> p = geti(i-1);
s.next = p.next; //在其后插入
if(p.next!=null){ //若后面结点不为空,则双向指向
p.next.prior = s;
}
p.next = s;
s.prior = p;
}
//实现元素的删操作,删除指定位置元素
public void Delete(int i){
if(i<0||i>size()-1){
throw new IllegalArgumentException("位置i不在有效范围之内!");
}
DoubleList1<Object> p = geti(i);
if(p.next!=null){
p.next.prior = p.prior;
}
p.prior.next = p.next;
}
//实现元素的改操作,修改指定位置的元素
public void SetElem(int i,Object e){
if(i<0||i>size()-1){
throw new IllegalArgumentException("位置i不在有效范围之内!");
}
DoubleList1<Object> p = geti(i);
p.data = e;
}
//实现元素的查操作,查找指定位置的元素
public Object getElem(int i){
DoubleList1<Object> p = head;
int j = -1;
while(j<i){
j++;
p = p.next;
}
return p.data;
}
//将双链表中全部元素转换为字符串并返回
public String ToString(){
String ans = "";
DoubleList1<Object> p = head.next;
while(p!=null){
ans+=p.data+" ";
p=p.next;
}
return ans;
}
}
public class DoubleList {
public static void main(String[] args) {
Integer[] a = {1,2,3,4,5};
DoubleList2<Integer> doubleList2 = new DoubleList2<>();
doubleList2.CreateHDList(a);
System.out.println(doubleList2.getElem(3));
System.out.println(doubleList2.ToString());
DoubleList2<Integer> doubleList3 = new DoubleList2<>();
doubleList3.CreatLDList(a);
System.out.println(doubleList3.ToString());
}
}
相关结果为:
这里我没有试验所有方法,读者可自行拷贝尝试。
这里使用泛型创建类及成员变量data,是方便各种类型插入,读者可自行替换成包装类类型。实现了基本功能之后,读者可自行设计案例做题。每个方法代码中都有对应注释内容。
若觉得内容稍可,请留下你们的
,若有疑问,欢迎下方评论区留言讨论!