# Lecture3 - 线性表
本章考点:
- 散列表、并查集、优先级队列
- 栈和队列考的概率比较大、数组和单链表也可能考
- 循环链表、双链表考的比较少
- 带表头结点的单链表 \ 不带表头结点的单链表
- 主要靠代码、性质
# 线性表
# 概述
- 线性表是对象或者值的集合
# 线性表需要实现的方法
# 线性表的不同实现
# 简单数组实现线性表
- (e1,e2,………en),也就是数组向系统请求了一块连续的内存,数据量大小由程序员来确定。
- each position of the array is called a cell or a node mapping formula: location(i)=i-1
- 从数组类型存储的线性表中取出一个元素的复杂度是 O (1)。
- 一般我们在线性表中放置的是相同类型的值。
# 方法实现
顺序查找
Search(x)
的时间复杂度 O (n), 平均算法复杂度 O ((n+1)/2)
- 平均数据访问次数:n/2
删除
remove(k,x)
:delete the k’th element and return it in x
- 最坏和平均情况下的算法复杂度都是 O (n)
- 平均数据移动次数:(n-1)/2
插入操作
insert(x,i)
- 平均数据移动次数:n/2
# 优缺点
printList 花费线性时间
findKth 花费常数时间
insert 和 delete 最多花费 O (n) 的时间
# 单链表 (Linked List) 实现线性表
- 特点:在内存中不是连续内存。
- 一个链表节点中存储一个指针,一个数据。
- java 中的指针是不可以进行加减操作,防止出现一些系统问题。而 C++ 中是可以进行加减运算。
- 最后的一个指针指向 null
# 部分操作
删除操作:
Delete(index,x)
- 删除第一个节点:重新指向第一个指针,并且在 C++ 中需要 delete 掉被解引用的对象。手动释放需要先记下来位置。
- 删除中间节点:首先查询,之后删除
before.link = before.link.link
插入操作:
insert(index,x)
- 在线性表开头插入一个元素:首先插入一个元素,然后把头指针指向头。
- 在线性表中间插入一个元素:首先查询找到相应元素
# 带有表头元素的单链表
有一个头节点,Header: 这个节点的数据是没有的,然后指针是指向第一个元素的
# 线性表的 java 实现
ListNode
:代表结点的类LinkedList
:代表表本身的类LinkedListItr
:代表游标位置的类- 都是包 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
- 封装相应的指针操作
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)
# 双链表
删除开始,删除中间是不同的
# 删除
# 插入
# 双向循环链表
不带表头的双向链表
带表头的双向链表
带表头的空双向链表
# 循环链表
# 例子
# 求解约瑟夫问题
问题解决:
- 使用新的单链表来记录。
- p 是最后一个点,降低插入的复杂度
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 为出列的最后一个。
删除后,将节点加入 出队列。
# 循环队列的相关计算公式
- 我们不妨设 front 为队头指针,rear 为队尾指针,m 为队列最大容量。
- 入队: rear = (rear + 1) % m
- 出队: front = (front + 1) % m
- 队空: front = rear
- 队满: front = (rear + 1) % m
- 当前队列中的元素个数: n = (rear - front + m) % m
- 求队头指针位置: (rear - length + 1 + m) % m
- 循环队列的相关计算公式
# 求解多项式问题
见 ppt
# 一些其他整理
# List 线性表
# LinearList 常见的操作
printList
makeEmpty
findKth
# 数组实现
printList 花费线性时间
findKth 花费常数时间
insert 和 delete 最多花费 O (n) 的时间
用于在高端插入,其后只发生对数组的访问(findKth)的操作
# 链表实现
linkedlist(单链表、双链表、循环链表)
printList 花费线性时间
findKth 效率比数组低,线性时间
insert 和 delete 花费常数时间
经常 insert 和 delete,查询较少
# Collection API 中的表
# Collection
这个接口并不规定集合如何决定 x 是否属于该集合
实现了 iterable 接口
# Iterable 接口
需要实现 iterator 方法,返回一个 Iterator 的对象。
不能对正在进行迭代的集合进行结构上的改变(add、remove 或 clear 方法)。
remove 表示删除由 next 最新返回的项,remove 不能够重复调用,除非在再次调用 next 之后。
# List 接口、ArrayList 类和 LinkedList 类
继承 Collection 接口,以下为新增的一些操作。
List 可由 ArrayList 和 LinkedList 类实现,Arraylist 提供一种可增长数组的实现,LinkedListi 提供了双链表的实现。
与前文的 Iterable 接口相呼应,不能随意在迭代时更改,但是可以 remove 刚刚 next 得到的值
#
# ListIterator 接口
新增了以上接口,可以实现从后向前的遍历。
# ArrayList 类的实现
关注后 ++ 和前 -- 的用法
postfix ++ operator
prefix -- operator
### 迭代器、java外部类、嵌套类和内部类
(?
# LinkedList 类的实现
# 栈 ADT
少量的操作却可以十分强大和重要
# 后缀表达式
用逆波兰记法或后缀记法
不必了解运算优先级,而可以利用栈的结构正确计算
# 中缀到后缀的转换
# 队列 ADT
循环数组实现