# Lecture3 - 线性表

本章考点:

  • 散列表、并查集、优先级队列
  • 栈和队列考的概率比较大、数组和单链表也可能考
  • 循环链表、双链表考的比较少
  • 带表头结点的单链表 \ 不带表头结点的单链表
  • 主要靠代码、性质

# 线性表

# 概述

  1. 线性表是对象或者值的集合

image-20230209113703729

image-20230209113541642

image-20230209113557197

# 线性表需要实现的方法

img

# 线性表的不同实现

# 简单数组实现线性表

  1. (e1,e2,………en),也就是数组向系统请求了一块连续的内存,数据量大小由程序员来确定。
  2. each position of the array is called a cell or a node mapping formula: location(i)=i-1
  3. 从数组类型存储的线性表中取出一个元素的复杂度是 O (1)
  4. 一般我们在线性表中放置的是相同类型的值。

image-20230209113855923

# 方法实现
  1. 顺序查找

    Search(x)
    

    的时间复杂度 O (n), 平均算法复杂度 O ((n+1)/2)

    • 平均数据访问次数:n/2

    image-20230209113917447

  2. 删除

    remove(k,x)
    

    :delete the k’th element and return it in x

    • 最坏和平均情况下的算法复杂度都是 O (n)
    • 平均数据移动次数:(n-1)/2

    image-20230209113939649

  3. 插入操作

    insert(x,i)
    
    • 平均数据移动次数:n/2

    image-20230209113957170

# 优缺点

image-20230209114131034

printList 花费线性时间

findKth 花费常数时间

insert 和 delete 最多花费 O (n) 的时间

# 单链表 (Linked List) 实现线性表

  1. 特点:在内存中不是连续内存。
  2. 一个链表节点中存储一个指针,一个数据。
    • java 中的指针是不可以进行加减操作,防止出现一些系统问题。而 C++ 中是可以进行加减运算。
  3. 最后的一个指针指向 null

image-20230209114227674

# 部分操作
  1. 删除操作:

    Delete(index,x)
    
    1. 删除第一个节点:重新指向第一个指针,并且在 C++ 中需要 delete 掉被解引用的对象。手动释放需要先记下来位置。
    2. 删除中间节点:首先查询,之后删除
      • before.link = before.link.link
  2. 插入操作:

    insert(index,x)
    
    1. 在线性表开头插入一个元素:首先插入一个元素,然后把头指针指向头。
    2. 在线性表中间插入一个元素:首先查询找到相应元素
# 带有表头元素的单链表

有一个头节点,Header: 这个节点的数据是没有的,然后指针是指向第一个元素的

# 线性表的 java 实现

  1. ListNode :代表结点的类
  2. LinkedList :代表表本身的类
  3. LinkedListItr :代表游标位置的类
  4. 都是包 DataStructure 的一部分

# ListNode

package DataStructures;
class ListNode {   
    object element;
    ListNode next;
    ListNode( object theElement) {
        this( theElement, null);
    }
    ListNode( object theElement, ListNode n) {
        element = theElement; 
        next = n;
    }
}

# LinkedListItr

  1. 封装相应的指针操作
package DataStructures
public class LinkedListItr {
    LinkedListItr( ListNode  theNode) {
        current = theNode;
    }
    public boolean isPastEnd( ) {
        return current == null; 
    }
    public object retrieve() {
        // 获得当前节点的数据
        return isPastEnd( ) ? null : current.element; 
    }
    public void advance( ) {
        if( ! isPastEnd( ) ) current = current.next; 
    }
    ListNode current; 
}

# LinkedList

public class LinkedList {
    private ListNode header;
    public LinkedList( ) {// 这里是含有表头节点的单链表,如果不带表头的话,应该是 heder = null
        header = new ListNode( null );
    }
    public boolean isEmpty( ) {
        return header.next = = null ;
    }
    public void makeEmpty( ) {
        header.next = null;
    }
    // 指向头指针的 Itr
    public LinkedListItr zeroth( ) {
        return new LinkedListItr( header );
    }
    // 指向第一个项的 Itr
    public LinkedListItr first( ) {
        return new LinkedListItr( header.next );
    } 
    public LinkedListItr find( object x )
    public void remove( object x )
    public LinkedListItr findPrevious( object x )
    public void insert( object x, LinkedListItr p )
}

# 一些方法的实现

# 打印线性表

//Method to print a list
public static void printList( LinkedList theList ) {   
    if ( theList.isEmpty( ) ) 
        System.out.print ("Empty list");
    else {
        LinkedListItr itr = theList.first();
        for(;! Itr.isPastEnd(); itr.advance( ) )
            System.out.print(itr.retrieve() + " " );
    }
    System.out.println();
}

# 查找特定项

public LinkedListItr find (object x) {
    ListNode itr = header.next;
    while ( itr != null && !itr.element.equals( x ))
        itr = itr.next;
    return new LinkedListItr( itr );
}

时间复杂度 O (n)

# 移除节点

public void remove( object x ) {
    LinkedListItr p = findprevious( x );
    if( p.current.next != null )
        p.current.next = p.current.next.next;
}

时间复杂度 O (1)

  • 你可以把 findPrevious 操作当做是一个单独的先进行的操作,而不是这个操作的一部分。

# 查找上一个节点

public LinkedListItr findPrevious( object x ) {
    ListNode itr = header;
    while( itr.next !=null && !itr.next.element.equals( x )) itr = itr.next;
    return new LinkedListItr( itr );
}

时间复杂度 O (n)

# 双链表

img

删除开始,删除中间是不同的

# 删除

  1. 删除第一个节点
    img
  2. 删除中间img

# 插入

  1. 从头插入
    img
  2. 从中插入
    img

# 双向循环链表

不带表头的双向链表

image-20230209115917465

带表头的双向链表

image-20230209115934941

带表头的空双向链表

image-20230209120024725

# 循环链表

image-20230209120128895

image-20230209120143942

# 例子

# 求解约瑟夫问题

约瑟夫问题
img

问题解决:

  • 使用新的单链表来记录。
  • p 是最后一个点,降低插入的复杂度

img

w = m;
for( int i = 1; i<= n-1; i++) {
    for (int j = 1; j<=w-1; j++) rear = rear.link;
    if (i = = 1) { 
        head = rear.link ; p = head;
    } else { 
        p.link = rear.link;
        p = rear.link;
    }
    rear.link = p.link;
 }
P.link = rear; rear.link = null;

head 为出列的第一个,p 为出列的最后一个。
删除后,将节点加入 出队列。

# 循环队列的相关计算公式

  1. 我们不妨设 front 为队头指针,rear 为队尾指针,m 为队列最大容量。

img

  1. 入队: rear = (rear + 1) % m
  2. 出队: front = (front + 1) % m
  3. 队空: front = rear
  4. 队满: front = (rear + 1) % m
  5. 当前队列中的元素个数: n = (rear - front + m) % m
  6. 求队头指针位置: (rear - length + 1 + m) % m
  7. 循环队列的相关计算公式

# 求解多项式问题

见 ppt

# 一些其他整理

# List 线性表

image-20221019160317148

# LinearList 常见的操作

image-20221019162747134

printList

makeEmpty

findKth

# 数组实现

printList 花费线性时间

findKth 花费常数时间

insert 和 delete 最多花费 O (n) 的时间

用于在高端插入,其后只发生对数组的访问(findKth)的操作

# 链表实现

linkedlist(单链表、双链表、循环链表)

printList 花费线性时间

findKth 效率比数组低,线性时间

insert 和 delete 花费常数时间

经常 insert 和 delete,查询较少

# Collection API 中的表

# Collection

这个接口并不规定集合如何决定 x 是否属于该集合

实现了 iterable 接口

image-20221019164503386

# Iterable 接口

需要实现 iterator 方法,返回一个 Iterator 的对象。

不能对正在进行迭代的集合进行结构上的改变(add、remove 或 clear 方法)。

remove 表示删除由 next 最新返回的项,remove 不能够重复调用,除非在再次调用 next 之后。

image-20221019164805466

image-20221019164819297

# List 接口、ArrayList 类和 LinkedList 类

继承 Collection 接口,以下为新增的一些操作。

image-20221019165100618

List 可由 ArrayList 和 LinkedList 类实现,Arraylist 提供一种可增长数组的实现,LinkedListi 提供了双链表的实现。

与前文的 Iterable 接口相呼应,不能随意在迭代时更改,但是可以 remove 刚刚 next 得到的值

image-20221019165823828

# image-20221019165859005

# ListIterator 接口

image-20221019170104724

新增了以上接口,可以实现从后向前的遍历。

# ArrayList 类的实现

关注后 ++ 和前 -- 的用法

postfix ++ operator

prefix -- operator

image-20221019171039212

 ### 迭代器、java外部类、嵌套类和内部类

(?

# LinkedList 类的实现

image-20221019172048281

# 栈 ADT

少量的操作却可以十分强大和重要

# 后缀表达式

用逆波兰记法或后缀记法

不必了解运算优先级,而可以利用栈的结构正确计算

# 中缀到后缀的转换

# 队列 ADT

image-20221019172855308

循环数组实现