第二课 线性表
1 线性表的类型定义
1.1 线性表的逻辑结构
线性表是一种典型的线性结构。 数据的运算是定义在逻辑结构上的,而运算的具体实现则是在存储结构上进行的。
(1)定义
线性表是由n(n≥0)个数据元素(结点)a1,a2, …an组成的有限序列。其中数据元素的个数n
定义为表的长度。当n=0时称为空表,常常将非空的线性表(n>0)记作: (a1,a2,…an) 。
这里的数据元素ai(1≤i≤n)只是一个抽象的符号,其具体含义在不同的情况下可以不同
(2)逻辑特征
1)对非空的线性表,有且仅有一个开始结点a1,它没有直接前驱,而仅有一个直接后继a2;
2)有且仅有一个终端结点an,它没有直接后继,而仅有一个直接前驱an-1;
3)其余的内部结点ai(2≤i≤n-1)都有且仅有一个直接前驱ai-1和一个直接后继ai+1。
1.2 线性表的物理结构
在下文具体的实现过程有详细介绍
2 线性表的顺序表示和实现
2.1 顺序表
2.1.1 定义
把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表
简称顺序表。由于在高级语言中的一维数组也是采用顺序存储表示,故可以用数组类型来描述
顺序表
2.1.2 基本操作
package chapter2.seqList;
public class SeqList {
/**
* 1.数据结构定义
*/
Object data[] ;//数组
int current;//当前数据长度
int capacity;//最大的长度,线性表的容量,在使用的时候,
//该顺序表的存储空间就固定这么多,不够的时候可以扩容
/**
* 2.初始化线性表:构造方法
*/
public SeqList(int initialSize){
if (initialSize >= 0) {
this.capacity = initialSize;
data = new Object[initialSize];
current = 0;
} else {
throw new RuntimeException("初始化大小不能小于0:" + initialSize);
}
}
/**
* 检查存储数据的数组容量,如果数组已经满,则扩充容量;否则不操作。
*/
private void ensureCapacity() {
int index;
if (current == capacity) {
capacity *= 2;
Object[] newData = new Object[capacity];
for(index = 0; index < current; ++index) {
newData[index] = data[index];
}
data = newData;
}
}
/**
* 验证下标值是否合法,非法时抛出异常
* @param index 待验证的下标值
*/
private void validateIndex(int index) {
if (index < 0 || index > current) {
throw new RuntimeException("无效的下标:" + index);
}
}
/**
*3.实现的操作:这里只是研究插入和删除操作,查询和修改太简单,不做研究
*
*/
/**
* 3.1插入操作
* 3.1.1算法分析
* 设表的长度为n。该算法的时间主要化费在循环的结点后移语句上,
* 该语句的执行次数(即移动结点的次数)是n-i+1。由此,所需移动
* 结点的次数依赖于
(1) 表的长度
(2) 插入位置
1)当i=n时,不移动->最好情况;
2) 当i=1时,需移动表中所有结点->最坏情况,
3)算法的平均移动
由于插入可能在表中任何位置上进行,在长度为n的线性表中第i个
位置上插入一个结点,令Eis(n)表示移动结点的期望值(即移动的平
均次数),则在第i个位置上插入一个结点的移动次数为n-i+1。 Pi代
表在第i个位置插入概率,则
Eis(n)= p1 ×n+p2 ×(n-1)+ ….+ pn × 1+pn+1 × 0
若表中任何位置(1≤i≤n+1)上插入结点的概率是均等的,则
p1=p2=p3=…=p n+1=1/(n+1)
因此,在等概率插入的情况下:
Eis(n)=1/(n+1)[n+(n-1)+…+1+0]=n/2
结论:
在顺序表上做插入运算,平均要移动表上一半结点。当表长n较大时,
算法的效率相当低。虽然Eis(n)中n的系数较小,但就数量级而言,它
仍然是线性阶的。因此算法的平均时间复杂度为O(n)。
*/
public boolean insert(int index,Object o){
//index从0开始
validateIndex(index);
ensureCapacity();
for (int temp = current-1; temp >=index; temp--) {
data[temp+1] = data[temp];
}
data[index] = o;
current++ ;
return true;
}
/**
* 3.2 删除操作
* 3.2.1 算法分析
* 线性表的删除运算是指将表的第i(1≤i≤n)结点删除,使长度为n的线性表:
* (a1,…a i-1,ai,a i+1…,an) 变成长度为n-1的线性表 (a1,…a i-1,a i+1,…,an)
* 算法分析与插入算法相似,结点的移动次数也是依赖:
* (1) 表的长度
(2) 插入位置
1)若i=n,无需移动结点;
2)若i=1,则前移元素n-1个
3)算法的平均移动
令Edl表示所需移动结点的平均次数,删除表中第i个结点的移动次数为n-i,
pi是删除i个元素的概率,则
Edl=p1 ×(n-1)+p2 ×(n-2)+ ….+pn-1 × 1+pn × 0
等概率的假设下:p1=p2=p3=…=pn=1/n
Edl=1/n[(n-1)+(n-2)+…+1+0]=(n-1)/2
结论:
即在顺序表上做删除运算,平均要移动表中约一半的结点,平均时间复杂度也是O(n)。
*/
public boolean delete(int index) {
validateIndex(index);
for (int temp = index; temp < current - 1; temp++) {
data[temp] = data[temp + 1];
}
data[current - 1] = null;
current--;
return true;
}
/**
* 3.3 返回下标为index的元素
* @param index 欲取得元素的下标
* @return
*/
public Object get(int index) {
validateIndex(index);
return data[index];
}
/**
*
*3.4 更改index位置的元素
*/
public boolean set(int index,Object o) {
validateIndex(index);
data[index] = o;
return true;
}
@Override
public String toString() {
String str = "[ ";
for (Object o : data) {
if (o != null) {
str += o + ",";
}
}
str += "]";
return str;
}
/**
* 4.测试
* @param args
*/
public static void main(String[] args){
SeqList s = new SeqList(5);
for(int i = 0 ; i<3;i++){
s.insert(i,i);
}
s.delete(1);
System.out.println(s.toString());
}
}
3 线性表的链式表示和实现
3.1 动态(单)链表
3.1.1 定义
链表是指用一组任意的存储单元来依次存放线性表的结点,这组存储单元即可以是
连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。因此,链
表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,
在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息,这
个信息称为指针(pointer)或链(link)。这两部分组成了链表中的结点结构。由于上述
链表的每一个结点只有一个链域,故将这种链表称为单链表(Single Linked)。并且链表
中节点数受到限制的,所以又是动态链表。显然,单链表中每个结点的存储地址是存
放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点。
同时,由于最后一个结点无后继,故结点的指针域为空,即NULL
3.1.2 基本操作
package chapter2.singleLinkList;
public class SingleLinkedList {
/**
* 1.结点类
*/
private static class Node{
Object nodeValue; // 数据域
Node next; // 指针域保存着下一节点的引用
Node(Object nodeValue, Node next) {
this.nodeValue = nodeValue;
this.next = next;
}
Node(Object nodeValue) {
this(nodeValue, null);
}
}
/**
* 2.单链表的成员变量
* head:是头节点
* tail:是尾节点
*/
private Node head, tail;
/**
* 3.构造方法:初始化单链表
*/
public SingleLinkedList() {
head = tail = null;
}
/**
* 判断链表是否为空
*/
public boolean isEmpty() {
return head == null;
}
/**
* 创建头指针,该方法只用一次!
*/
public void addToHead(Object item) {
head = new Node(item);
if(tail == null) tail = head;
}
/**
* 添加尾指针,该方法使用多次
*/
public void addToTail(Object item) {
if (!isEmpty()) { // 若链表非空那么将尾指针的next初使化为一个新的元素
tail.next = new Node(item); // 然后将尾指针指向现在它自己的下一个元素
tail = tail.next;
} else { // 如果为空则创建一个新的!并将头尾同时指向它
head = tail = new Node(item);
}
}
/**
* 从下面的讨论可以看出,链表上实现插入和删除运算,无须移动结点,仅需修改指针,
* 所以对于插入和删除操作,效率会快很多
*/
/**
* 4.插入
*/
/**
* 4.1从表头插入
*/
public void addFirst(Object item) {
Node newNode = new Node(item);
newNode.next = head;
head = newNode;
}
/**
* 4.2从表尾插入
*/
public void addLast(Object item) {
Node newNode = new Node(item);
Node p = head;
while (p.next != null) p = p.next;
p.next = newNode;
newNode.next = null;
}
/**
* 4.3 在表指定节点之前插入节点:
* 插入操作可能出现4种情况:
* 1.表示空的,插入失败
* 2.指定的数据不存在,插入到表末尾
* 3.指定的数据是第一个节点,插入到末尾
* 4.指定的数据在中间,插入到指定的数据前面
* 由上面可以知道,这种方法主要是针对第4种情况
*/
public boolean insert(Object appointedItem, Object item) {
Node prev = head, curr = head.next, newNode;
newNode = new Node(item);
if(!isEmpty()) {
while((curr != null) && (!appointedItem.equals(curr.nodeValue))) { //两个判断条件不能换
prev = curr;
curr = curr.next;
}
newNode.next = curr; //②③④
prev.next = newNode;
return true;
}
return false; //①
}
/**
* 5.删除
*/
/**
* 5.1表头删除
*/
public void removeFirst() {
if (!isEmpty()) head = head.next;
else System.out.println("此链表是空链表!");
}
/**
* 5.2表尾删除
*/
public void removeLast() {
Node prev = null, curr = head;
while(curr.next != null) {
prev = curr;
curr = curr.next;
if(curr.next == null) prev.next = null;
}
}
/**
* 5.3 删除指定节点
* 可能出现的情况:
* 1.找不到指定的数据,删除失败
* 2.要删除的数据是第一个
* 3.要删除的数据出现在中间
*/
public void remove(Object item) {
Node curr = head, prev = null;
boolean found = false;
while (curr != null && !found) {
if (item.equals(curr.nodeValue)) {
if(prev == null) removeFirst();
else prev.next = curr.next;
found = true;
} else {
prev = curr;
curr = curr.next;
}
}
}
/**
* 返回此列表中首次出现的指定元素的索引,如果列表中不包含此元素,则返回 -1.
* 时间复杂度是O(n)
*/
public int indexOf(Object item) {
int index = 0;
Node p;
for(p = head; p != null; p = p.next) {
if(item.equals(p.nodeValue))
return index;
index++;
}
return -1;
}
/**
* 6.判断是否包含指定元素
*
* 如果此列表包含指定元素,则返回 true。
*/
public boolean contains(Object item) {
return indexOf(item) != -1;
}
/**
*7. 打印列表
*/
public void printList() {
if (isEmpty()) {
System.out.println("null");
} else {
for(Node p = head; p != null; p = p.next)
System.out.println(p.nodeValue);
}
}
/**
* 8.测试
*/
public static void main(String[] args) {
SingleLinkedList t = new SingleLinkedList();
t.addToHead("A");
//t.addFirst("addFirst");
t.addToTail("B");
t.addToTail("C");
System.out.println(t.indexOf("C")); // 2
System.out.println(t.contains("A")); // true
//t.addLast("addLast");
//t.removeLast();
//t.insert("B", "insert");
//t.removeFirst();
//t.remove("B"); // A C
t.printList(); // A B C
}
}
3.2 静态(单)链表
3.2.1 定义
用一维数组来实现线性链表,这种用一维数组表示的线性链表,称为静态链表。
线性链表的区别是他存的是元素的下标,而不是地址。相关概念:
静态:体现在表的容量是一定的。(数组的大小);
链表:节点的逻辑顺序和物理顺序不一致,插入与删除同前面所述的动态链表方法相同
3.2.2 基本操作
package chapter2.singleLinkList;
import java.util.Scanner;
public class StaticLinkedList {
/**
* 1.节点类
* @author Administrator
*
*/
class Element //相当于链表中的空间单元
{
int data; //记录存入的数据
int next; //记录下一个元素的下标
}
/**
* 2.成员变量
*/
Element[] link = null; //保存数据的数组
int length = 0; //记录当前装有用数据的大小,非空闲空间数
int MAXSIZE = 0; //记录静态链表可装的最大空间
int current = 0; //记录当前可用空闲容器的下标 ,空闲单元也组成一个链表,
//他记录的就是第一个空闲的的单元
int head = 0; //记录当前静态链表头的下标
/**
* 3.构造方法:初始化静态链表
*/
public StaticLinkedList(int size) //初始化静态列表
{
link = new Element[size];
for(int i = 0; i < size; i++)
{
link[i] = new Element();
link[i].data = -1;
link[i].next = i + 1; //下标0的单元指向下标1的单元,一开始逻辑顺序和物理顺序一致
}
current = 0;
head = 0;
length = 0;
MAXSIZE = size;
System.out.println("初始化静态链表...");
}
/**
* 向当前静态链表的后面添加一个数据
* @param data
* @return
*/
public int add(int data)
{
if(length < MAXSIZE) //检查是否还有空闲空间可以分配数据,length是实际的有效长度
{
link[current].data = data; //将当前空闲单元存入数据
current = link[current].next; //将current指向下一个空闲单元
length++; //实际使用空间增加1
System.out.println("成功添加数据:" + data);
return 0;
}
System.out.println("添加数据失败");
return -1;
}
/**
* 获取给定下标的实际下标 :index表示逻辑上在链表(无序)的第index位置,
* 而在数组中不一定就是index,因为链表是无序的,这里获取的就是他在数组中
* 的位置
* @param index
* @return
*/
public int getTrulyIndex(int index)
{
int i = 0; //表示元素在链表的逻辑位置
int tHead = head; //表示表头元素的数组下标
if(link != null)
{
while(i < index) //这个是实现的重要代码
{
tHead = link[tHead].next; //每次执行这个语句的时候获取下一个真正的位置下标
i++;
}
if(i == index) //判断否是获得真正的下标
{
return tHead; //返回真正的下标
}
}
return -1;
}
/**
* 删除当前静态链表最后一个数据:不一定是数组的最后一个元素
*/
public int delete()
{
if(length > 0 && link != null) //判断是否有数据可以删除
{
int temp = current; //暂时记录当前空闲单元的下标
current = getTrulyIndex(length - 1); //将要删除的使用单元加入到空闲单元链中
link[current].next = temp; //将这个刚加入的单元链接上空闲空间
length--; //实际使用空间自减1
System.out.println("成功删除数据:" + link[current].data);
return 0;
}
System.out.println("删除数据失败");
return -1;
}
/**
* 4.插入:在指定的位置插入数据
* 这里的index是指元素在链表的第index位置,不是数组的下标,
* 插入的时候只是需要把元素放到空闲的空间就可以了,然后修改指针,不需要移动元素
*/
public int insert(int data, int index) //从指定的下标的地方插入数据
{
if(length < MAXSIZE && link != null && index < length && index >= 0) //判断是否有空间可以插入新数据和判断当前输入的下标是否在范围内
{
int tCurrent = current; //保存当前空闲单元的下标
current = link[current].next; //将current指向下一个空闲单元
//上面这两个语句。相当于在空闲链中拿出一个单元出来储存数据
if(index == 0) //当要插入的位置在最前面
{
link[tCurrent].data = data; //将拿出的空闲单元存入数据
link[tCurrent].next = head; //将这个单元指向原来的头位置下标
head = tCurrent; //重新设置head指向的位置
}
else if(index == length - 1) //当要插入的是在静态链表末端
{
link[tCurrent].data = data; //放入数据
}
else //要插入的位置前后都有空间单元的时候
{
int preIndex = getTrulyIndex(index - 1); //获取要插入的前一个空间单元的index
link[tCurrent].next = link[preIndex].next; //将要插入位置的前一个单元原来的cur赋值给现在要插入的单元的cur(cur:保存下一个单元的位置下标)
link[tCurrent].data = data; //放入数据
link[preIndex].next = tCurrent; //将要插入位置的前一个单元指向现在要插入单元的位置
}
length++; //大小自增1
System.out.println("成功在 " + index + " 插入数据: " + data);
return 0;
}
System.out.println("在" + index + "插入数据失败");
return -1;
}
/**
* 5.删除:删除给定位置的数据
* 这里的index是指元素在链表的第index位置,不是数组的下标,
* 删除的时候只是需要把元素放到空闲的空间就可以了,然后修改指针,不需要移动元素
*/
public int delete(int index) //删除给定位置的数据
{
//判断是否有数据可以删除
if(length > 0 && link != null && index >= 0 && index <= length - 1)
{
if(index == 0) //判断是否要删除第一个单元
{
int tHead = head; //保存当前的head
head = link[head].next; //将head指向一下单元
link[tHead].next = current; //将删除的单元加入空闲链表
current = tHead; //将current指向空闲链的第一个单元
}
else if(index == (length - 1)) //判断是否要删除最后一个单元
{
int last = getTrulyIndex(length - 1); //获取最后一个单元的位置
if(last != -1) //如果获取位置成功
{
link[last].next = current; //将删除的单元加入空闲链中
current = last; //将current指向空闲链的第一个单元
}
}
else //判断是否要删除第一个到最后一个(单元)之间的单元
{
int preIndex = getTrulyIndex(index - 1); //获取要删除单元的前一个单元的位置
int temp = current; //保存current的值
current = link[preIndex].next; //将要删除的单元链入空闲链
link[preIndex].next = link[current].next; //将上一个单元重新指向到下一个单元,例如A、B、C 删除 B ,这个语句相当于 ##将 A 从指向 B 重新指向为 C ##(即改变A的指向)
link[current].next = temp; //将删除单元链入空闲链
}
length--; //大小自减1
System.out.println("成功在 " + index + " 删除数据: " + link[current].data);
return 0;
}
System.out.println("在" + index + "删除数据失败");
return -1;
}
/**
* 6.获取给定下标储存数据
* 这里的index是指元素在链表的第index位置,不是数组的下标,
* 插入的时候只是需要把元素放到空闲的空间就可以了,然后修改指针,不需要移动元素
*
*/
public int get(int index) //获取给定下标储存数据
{
int tIndex = getTrulyIndex(index);
if(tIndex != -1 && link != null)
{
return link[tIndex].data;
}
return -1;
}
/**
* 7.测试
*/
public static void main(String[] args) //这里的测试代码,与静态链表没关系
{
int size = -1;
int[] original = null;
if(args != null && args.length == 1)
{
size = Integer.valueOf(args[0]);
}
else
{
Scanner sc = new Scanner(System.in);
System.out.print("输入测试大小<大于 10以上 >: ");
size = sc.nextInt();
}
while(size == -1)
{
System.out.print("输入错误,请重新输入: ");
Scanner sc = new Scanner(System.in);
size = sc.nextInt();
}
original = new int[size - 5];
StaticLinkedList sl = new StaticLinkedList(size);
for(int i = 0; i < size - 5; i++)
{
sl.add(i);
original[i] = i;
}
sl.delete();
if(size > 3)
{
sl.insert(20, 2);
sl.delete(1);
}
else
{
sl.insert(20, 0);
sl.delete(0);
}
System.out.print("\n操作前的链表内容: ");
for(int i = 0; i < original.length; i++)
{
System.out.printf("%d ",original[i]);
}
System.out.print("\n操作后的链表内容: ");
for(int j = 0; j < sl.length; j++)
{
System.out.printf("%d ",sl.get(j));
}
}
}
3.3 循环(单)链表
3.3.1 定义
循环链表是一种头尾相接的链表。其特点是无须增加存储量,仅对表的链接方式稍作改
变,即可使得表处理更加方便灵活。在单链表中,将终端结点的指针域NULL改为指向表
头结点的或开始结点,就得到了单链形式的循环链表,并简单称为单循环链表。
在很多实际问题中,表的操作常常是在表的尾位置上进行,此时头指针表示的单循环链
表就显得不够方便.如果改用尾指针rear来表示单循环链表,则查找开始结点a1和终端结
点an都很方便,它们的存储位置分别是rear–>next—>next和rear,显然,查找时间都是
O(1)。因此,实际中也常采用尾指针表示单循环链表。
3.3.2 基本操作
循环链表常常在表头加入一个空节点,不计算在链表的长度内,这样当在链表的
第一个位置插入节点的时候就不用修改尾节点,因为新节点放在了这个空节点之后,
而尾节点的下一个节点始终是空的头节点
package chapter2.cycleLinkList;
/**
* 和普通单链表不同的是,尾节点的下一个节点是头节点,而不是null
* @author Administrator
*
*/
/**
* 注意链表的长度是不包括表头节点的
* @author Administrator
*
*/
public class CycleLinkedList {
/**
* 1.结点类
*/
private static class Node{
Object nodeValue; // 数据域
Node next; // 指针域保存着下一节点的引用
Node(Object nodeValue, Node next) {
this.nodeValue = nodeValue;
this.next = next;
}
Node(Object nodeValue) {
this(nodeValue, null);
}
public Object getNodeValue() {
return nodeValue;
}
public void setNodeValue(Object nodeValue) {
this.nodeValue = nodeValue;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
/**
* 2.成员变量
*/
private Node head;//头节点:数据域为空,不是第一个开始节点,第二个才是链表的第一个
//元素
private int length;//链表长度,空的头节点不计算在内
/**
* 3.构造方法:初始化链表
*/
public CycleLinkedList() {
head = new Node(null, head);
length = 0;
}
/**
* 4.在头节点之前添加一个节点
*/
public void insertAtPrior(Object item) {//在头节点之后加一个节点
Node node = new Node(item, null); //encpsule the item to an node
node.setNext(head.getNext()); //node.next = head.next
head.setNext(node); //head.next = node
length ++;
}
public boolean isEmpty() {
return length==0;
}
/**
* 5.在最后节点上添加一个节点
* @param item
*/
public void add(Object item) {//在最后节点上添加一个节点
Node tmp = head;
if (isEmpty()) { // if the list is null
Node node = new Node(item, head); // .. if next == null ?
head.setNext(node);
} else {
Node node = new Node(item, head);
// find the end node of the list
while (head != tmp.getNext()) {
tmp = tmp.getNext();
}
tmp.setNext(node);
}
length++;
}
/**
* 6.在指定节点之前添加一个节点:略
*/
/**
*7. 删除第一个开始节点
*/
public void removeFromFirst() {
if (length < 1) { // if the list is null
System.out.println("The list is null and you can not delete");
} else if (1 == length) {
head.setNext(head);
length--;
} else {
head.setNext(head.getNext().getNext());
length--;
}
}
/**
* 8.删除最后一个节点
*/
public void removeFromLast() {//删除最后一个节点
if (length < 1) { // if the list is null
System.out.println("The list is null and you can not delete");
} else if (1 == length) {
head.setNext(head);
length--;
} else {
Node tmp1 = head;
Node tmp2 = head.getNext(); //set tmp2 -tmp1 = 1
while ( tmp2.getNext() !=head) {
tmp2 = tmp2.getNext();
tmp1 = tmp1.getNext();
}
tmp1.setNext(head);
length--;
}
}
/**
* 9.删除指定的节点:略
*/
/**
* 10.打印链表
*/
public void display() {
if (length < 1) {
System.out.println("The list is null");
} else {
Node tmp = head;
while (head != tmp.getNext()) {
tmp = tmp.getNext();
System.out.print(tmp.getNodeValue() + " ");
}
}
}
/**
* 11.测试
*/
public static void main(String[] args) {
CycleLinkedList l = new CycleLinkedList();
System.out.println(l.isEmpty());
l.add(1);
l.add(2);
l.insertAtPrior(3);
l.add(5);
System.out.println("the list is : ");
l.display();
System.out.println();
System.out.println("the length is :" + l.length);
l.removeFromFirst();
l.removeFromLast();
l.display();
}
}
4 双向链表
4.1 定义
双向链表(Double linked list):在单链表的每个结点里再增加一个指向其直接前趋的指针
域prior。这样就形成的链表中有两个方向不同的链,故称为双向链表。双链表一般由
头指针唯一确定的,将头结点和尾结点链接起来构成循环链表,并称之为双向链表。
4.2 基本操作
package chapter2.doubleLinkedList;
public class DoubleLinkedList {
/**
* 1.节点类
*/
// 节点类Node
private static class Node
{
Object value;
Node prev = this;
Node next = this;
Node(Object v)
{
value = v;
}
public String toString()
{
return value.toString();
}
}
/**
* 2.成员变量
*/
private Node head = new Node(null); // 头节点,空的,不作为链表的第一个节点
private int size; // 链表大小
/**
* 3.获取链表指定位置的节点
*/
private Node getNode(int index)
{
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException();
Node node = head;
for (int i = 0; i < index; i++)
node = node.next;
return node;
}
/**
* 4.在指定节点之前添加节点
*/
private void addBefore(Node newNode, Node node)
{
newNode.next = node;
newNode.prev = node.prev;
newNode.next.prev = newNode;
newNode.prev.next = newNode;
size++;
}
/**
* 5.在指定的节点之后添加指定节点
*/
private void addAfter(Node newNode, Node node)
{
newNode.prev = node;
newNode.next = node.next;
newNode.next.prev = newNode;
newNode.prev.next = newNode;
size++;
}
/**
* 6.删除指定节点
*/
private void removeNode(Node node)
{
node.prev.next = node.next;
node.next.prev = node.prev;
node.prev = null;
node.next = null;
size--;
}
/**
* 7.在第一个开始节点(不是表头节点)之前添加一个节点
*/
public boolean addFirst(Object o)
{
addAfter(new Node(o), head);
return true;
}
/**
* 8.在表尾的后面添加一个节点
*/
public boolean addLast(Object o)
{
addBefore(new Node(o), head);
return true;
}
/**
* 9.在指定位置添加一个节点
*/
public boolean add(int index, Object o)
{
addBefore(new Node(o), getNode(index));
return true;
}
/**
* 10.删除指定索引下的节点
*/
public boolean remove(int index)
{
removeNode(getNode(index));
return true;
}
/**
*11. 删除第一个开始节点
* @return
*/
public boolean removeFirst()
{
removeNode(head.next);
return true;
}
/**
* 12.删除最后一个节点
* @return
*/
public boolean removeLast()
{
removeNode(head.prev);
return true;
}
/**
* 13.打印链表
*/
@Override
public String toString()
{
StringBuffer s = new StringBuffer("[");
Node node = head;
for (int i = 0; i < size; i++)
{
node = node.next;
if (i > 0)
s.append(", ");
s.append(node.value);
}
s.append("]");
return s.toString();
}
/**
* 14.测试
*/
public static void main(String[] args)
{
DoubleLinkedList dll = new DoubleLinkedList();
//添加
dll.addLast("张曼玉");
dll.addLast("钟楚红");
dll.addLast("刘嘉玲");
System.out.println(dll);
//添加到最前
dll.addFirst("林青霞");
System.out.println(dll);
//添加到最后,同添加
dll.addLast("梅艳芳");
System.out.println(dll);
//添加到指定位置
dll.add(4, "王祖贤");
System.out.println(dll);
//移除最前的
dll.removeFirst();
System.out.println(dll);
//移除最后的
dll.removeLast();
System.out.println(dll);
//移除指定位置上的
dll.remove(2);
System.out.println(dll);
//返回指定位置上的元素
System.out.println(dll.getNode(1));
}
}