Java数据结构——线性表Ⅲ

一、双链表(Double Linked List)

1. 结点与链表类定义(设计思路)

(1)DLinkNode 结点类设计
  • 双向指针
  • prior 指针指向前驱,支持反向遍历
  • next 指针指向后继,支持正向遍历
  • 应用场景:需要频繁前后移动的场景(如文本编辑器的光标移动)
(2)DLinkListClass 链表类设计
  • 头结点 dhead
  • prior 初始为 null(链表头部标识)
  • next 初始为 null(链表尾部标识)
  • 双向操作基础:每个结点可通过 prior 和 next 双向访问

// 双链表结点泛型类(逻辑:双向指针实现双向遍历)

class DLinkNode<E> {

E data;

DLinkNode<E> prior; // 前驱指针

DLinkNode<E> next; // 后继指针

public DLinkNode() {

prior = null;

next = null;

}

public DLinkNode(E d) {

data = d;

prior = null;

next = null;

}

}

// 双链表泛型类(逻辑:头结点管理双向链表)

public class DLinkListClass<E> {

DLinkNode<E> dhead; // 头结点

public DLinkListClass() {

dhead = new DLinkNode<E>();

dhead.prior = null;

dhead.next = null;

}

// 基本运算算法...

}

2. 核心操作实现(逻辑解析)

(1)插入操作(在 p 结点后插入 s 结点)
  • 四步指针修改
  1. s.next = p.next (新结点后继指向原后继)
  2. if (p.next!=null) p.next.prior = s (原后继前驱指向新结点)
  3. p.next = s (原结点后继指向新结点)
  4. s.prior = p (新结点前驱指向原结点)
  • 顺序原则:先修改新结点的后继和原后继的前驱,再修改原结点的后继和新结点的前驱

s.next = p.next; // 新结点后继指向原后继

if (p.next != null) {

p.next.prior = s; // 原后继前驱指向新结点(非空时)

}

p.next = s; // 原结点后继指向新结点

s.prior = p; // 新结点前驱指向原结点
(2)删除操作(删除 p 结点)
  • 两步指针修改
  1. if (p.next!=null) p.next.prior = p.prior (后继前驱指向原前驱)
  2. p.prior.next = p.next (前驱后继指向原后继)
  • 内存处理:Java 自动回收被删除结点

if (p.next != null) {

p.next.prior = p.prior; // 后继前驱指向原前驱

}

p.prior.next = p.next; // 前驱后继指向原后继

二、循环链表(Circular Linked List)

1. 循环单链表(约瑟夫问题解决方案)

(1)问题建模思路
  • 数据结构选择
  • 循环单链表天然适合环形结构
  • 不带头结点设计,简化首尾相连逻辑
  • 报数逻辑
  • 从 first 开始遍历,计数到 m-1 时找到出列结点前驱
  • 出列后将 first 指向出列结点的后继,继续报数
(2)代码实现(逻辑步骤)

构建 n 个结点的循环单链表

循环 n 次执行出列操作:

  • 找到第 m-1 个结点 p
  • 出列结点 q=p.next
  • 删除 q 并更新 first 指针

class Child {

int no;

Child next;

public Child(int no1) {

no = no1;

next = null;

}

}

class Joseph {

int n, m;

Child first; // 指向开始报数的结点

// 构造方法:构建循环单链表

public Joseph(int n1, int m1) {

Child p, t;

n = n1; m = m1;

first = new Child(1); // 首结点编号1

t = first;

for (int i = 2; i <= n; i++) {

p = new Child(i);

t.next = p; t = p; // 尾插法建表

}

t.next = first; // 尾结点指向首结点,形成环

}

// 求解约瑟夫序列

public String Jsequence() {

String ans = "";

int i, j;

Child p, q;

for (i = 1; i <= n; i++) { // 出列n次

p = first;

j = 1;

while (j < m - 1) { // 找到第m-1个结点

j++;

p = p.next;

}

q = p.next; // 第m个结点(出列结点)

ans += q.no + " ";

p.next = q.next; // 删除q结点

first = p.next; // 从下一个结点开始报数

}

return ans;

}

}

三、顺序表与链表的比较(应用决策逻辑)

1. 空间效率决策树

是否已知数据量大小?

├─ 是 → 数据量小:顺序表(存储密度1)

└─ 否 → 动态变化:链表(动态分配)

2. 时间效率决策树

主要操作类型?

├─ 查找为主 → 顺序表(O(1)随机访问)

└─ 插入删除为主 → 链表(O(1)指针修改)

3. 综合对比表

场景特征

顺序表更适合

链表更适合

数据量固定

√(预先分配空间无浪费)

×(动态分配反而效率低)

频繁随机访问

√(下标直接访问)

×(需从头遍历)

频繁插入删除

×(大量元素移动)

√(仅修改指针)

内存碎片化严重

×(需要连续空间)

√(离散空间可用)

数据量动态增长

×(扩容代价高)

√(逐个结点分配)

要点

  • 指针操作三原则
  1. 先保存后继指针再修改(防止断链)
  2. 插入时 "先连后断",删除时 "先断后连"
  3. 头插法逆序,尾插法顺序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值