目录
2.3线性表的类型定义
由n(n>=0)个数据特性相同的元素构成的有限序列,称为线性表。
2.4线性表的顺序表示和实现
2.4.1线性表的顺序表示
线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或者映象。通常,称这种存储结构的线性表为顺序表(Sequential List),其特点是:逻辑上相邻的数据元素,其物理位置也是相邻的。
只要确定了存储线性表的起始位置,线性表中任一数据元素就可以随机存储,所以线性表的顺序存储结构是一种随机存储的存储结构。
由于高级程序设计语言中的数组类型也具有随机存储的特性,因此通常都用数组来描述数据结构中的顺序存储结构。
注意的是,我们用Java语言作为示范。
2.4.2顺序表中基本操作的实现
1.初始化
顺序表的初始化操作就是构造一个空的顺序表。
代码实现:
public class Sqlist {
public int [] elem;
public int length;
public final int MAXSIZE = 100;
public void InitSqlist(Sqlist L){
L.elem = new int [L.MAXSIZE];
L.length = 0;
}
}
2.取值
取值操作是根据指定的位置序号i,获取顺序表中第i个数据元素的值。
代码实现:
public int GetElem(Sqlist L, int i){
if (i>1&&i< L.length)return elem[i - 1];
return -1;//查找失败
}
3.查找
查找操作是根据指定的元素e,查找顺序表中第一个值与e相等的元素。若查找成功,则返回该元素在表中的序号,若查找失败则返回-1。
代码实现:
public int LocateElem(Sqlist L, int e) {
for (int i = 0; i < L.length; i++) {
if (L.elem[i] == e) return i + 1;
}
return -1;
}
4.插入
线性表的插入操作是指在表的第i个位置插入一个新的数据元素e,使得长度为n的线性表变成长度为n+1的线性表。
同时相应元素的位置也要后移一位。
代码实现:
public boolean ListInsert(Sqlist L,int i,int e){
if (i >= 1&&i < L.length&& L.length != MAXSIZE){
for (int j = L.length - 1;j < i -1;j--){
L.elem[j + 1] = L.elem[j];
L.elem[i - 1] = e;
++L.length;
}
return true;
}
return false;
}
5.删除
线性表的删除操作是指将表的第i个元素删去,将长度为n的线性表变成长度为n-1的线性表。
同样的相应元素的位置也要前移一位。
代码实现:
public boolean ListDelete(Sqlist L,int i){
if (i >= 1&&i <= L.length){
for (int j = i;j <= L.length - 1;j++){
L.elem[j - 1] = L.elem[j];
--L.length;
}
return true;
}
return false;
}
通过上面的操作,我们可以知道,在进行插入或者删除操作时,需移动大量数据元素,另外由于数组的长度有相对固定的静态特性,当表中数据元素个数较多且变化较大时,操作过程相对复杂,必然导致存储空间的浪费。这是数组的特性所带来的缺点。
而所有的这些问题,我们都可以通过线性表的另一种表示方法——链式存储结构来解决。
2.5线性表的链式表示和实现
2.5.1单链表的定义和表示
线性表链式存储结构的特点是:用一组任意的存储单元存储线性表的数据元素。对数据元素e来说,除了存储其本身的信息之外,还需存储一个指向其后继的信息,这两部分构成数据元素e的存储映像,称为节点(node)。它包括两个域,其中存储数据元素信息的域叫做数据域,存储直接后继存储位置的域叫做指针域。指针域中存储的信息称作指针或链。n个节点链接成一个链表。
由于此链表的每个节点只包含一个指针域,又称线性链表或者单链表。
链表在Java语言中的描述:
class Node{
public int data;//数据域
public Node next;//指针域
}
为了处理方便,在单链表的第一个节点之前附设一个节点,称为头节点。
- 首元节点:链表中存储第一个数据元素的节点。
- 头节点:在首元节点前的一个节点,其指针域指向首元节点。
- 头指针:指向链表中第一个节点的指针。
我们知道在顺序表中只要确定了存储线性表的起始位置,线性表中任一数据元素就可以随机存储,所以线性表的顺序存储结构是一种随机存储的存储结构。
但是在单链表中,由于结构的不同,要取得第i个数据元素必须从头指针出发逐个查找,也称为顺序存储的存储结构。
2.5.2单链表基本操作的实现
1.初始化
单链表的初始化操作就是构造一个空表。
代码实现:
boolean InitList(Node L){
L = new Node();
L.next = null;
return true;
}
2.取值
由于线性表是顺序存储结构,因此我们只能从首元节点出发,逐个节点访问。
代码实现:
int GetElem(Node L,int i){
Node p = L.next;
int j = 0;
while (p!=null&&j<i){
p = p.next;
++j;
}
int e = p.data;
return e;
}
3.查找
查找与线性表类似,从首元节点出发,依次查找。
代码实现:
Node LocateElem(Node L,int e){
Node p = L.next;
while (p != null){
if (p.data == e){
return p;
}
else p = p.next;
}
return null;
}
4.插入
假设要在数据元素a,b之间插入一个新的元素e,首先要生成一个数据域为e的新节点,再将新的节点插入到a,b之间。
修改a的指针域指向e,e的指针域指向b。
a.next = p.next;e.next = b;
代码实现:
boolean ListInsert(Node L,int i,int e){
Node p = L;
int j = 0;
while (p != null&&j < i){
p = p.next;
++j;
}
Node s = new Node();
s.data = e;
s.next = p.next;
p.next = s;
return true;
}
5.删除
删除链表中指定元素,需要找到该元素的前驱节点,修改该节点的指针域同时释放删除元素节点的空间。
代码实现:
boolean DeleteList(Node L,int i){
Node p = L;
int j = 0;
while (p.next != null&&j < i - 1){
p = p.next;
++j;
}
p.next = p.next.next;
return true;
}
6.创建单链表
根据节点插入的位置不同,链表的创建方法可分为前插法和后插法。
6.1前插法
前插法是指通过将新节点插入到链表的头部来创建链表。
代码实现:
boolean CreatList_Head(Node L,int n){
L = new Node();
L.next = null;
for (int i = 0; i < n; i++) {
Node p = new Node();
Scanner sc = new Scanner(System.in);
p.data = sc.nextInt();
p.next = L.next;
L.next = p;
}
return true;
}
6.2后插法
后插法是指通过将新节点插入到链表的尾部来创建链表。
代码实现:
boolean CreatList_Back(Node L,int n){
L = new Node();
L.next = null;
Node r = L;
for (int i = 0; i < n; i++) {
Node p = new Node();
Scanner sc = new Scanner(System.in);
p.data = sc.nextInt();
p.next = null;
r.next = p;
r=p;
}
return true;
}
}
2.5.3循环链表
循环链表(Circular Linked list)是另一种形式存在的链式存储结构。其特点是表中最后一个节点的指针域指向头节点,整个链表形成一个环。
2.5.4双向链表
双向链表(Double Linked List)的节点中有两个指针域 ,一个指向直接后继,一个指向直接前驱。
在Java语言中可以这样描述:
class DuNode{
public int data;//数据域
public DuNode prior;//指向直接前驱
public DuNode next;//指向直接后继
}
双向链表的插入:
public boolean ListInsert_Double(DuNode L,int i,int e){
//插入a,b之间
//a s b
if (!GetElem(L,i))return false;
DuNode s = new DuNode();
s.data = e;
s.prior = b.prior;
b.prior.next = s;
s.next = b;
b.prior = s;
return true;
}
双向链表的删除:
public boolean ListDelete_Double(DuNode L,int i,int e){
//a s b
if (!GetElem(L,i))return false;
s.prior.next = s.next;
s.next.prior = s.prior;
return true;
}
2.6顺序表和链表的比较
2.6.1空间性能的比较
顺序表的空间必须预先分配,容易浪费空间或溢出现象。而链表不需要事先分配空间。
因此当线性表长度变化较大时,采用链表作为存储结构。
2.6.2时间性能的比较
基于结构的不同,顺序表查找的时间复杂度为,而链表查找的时间复杂度为。
线性表的主要操作是和元素位置紧密相关的一类取值操作,因此很少做插入或者删除操作时应采取线性表。对于频繁进行插入或者删除操作的,应采用链表。
2.7线性表的应用
2.7.1线性表的合并
合并两个线性表,将LB中不存在与LA中的数据元素插入到LA的末尾并扩大LA的长度即可。
代码实现:
public void MergeList(Sqlist LA,Sqlist LB){
int m = LA.length;
int n = LB.length;
for (int i = 1; i <= n ; i++) {
int e = GetElem(LB,i);
if (!LocateElem(LA,e))ListInsert(LA,++m,e);
}
}
2.7.2有序表的合并
若线性表中的数据元素可以相互比较,并且数据元素在线性表中按照非递减(递增)顺序由于排列,就称为有序表(Ordered List)。
首先是顺序有序表的合并,考虑到有序性,我们可以采取归并排序的方法。
归并排序,是创建在归并操作上的一种有效的排序算法。算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。归并排序思路简单,速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。
迭代法:
① 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列。
② 设定两个指针,最初位置分别为两个已经排序序列的起始位置。
③ 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
④ 重复步骤③直到某一指针到达序列尾。
⑤ 将另一序列剩下的所有元素直接复制到合并序列尾。
对于两个有序表LA,LB并且LA,LB都是按照非递减排列的。
我们可以创建一个长度为两个有序表长度之和的新表LC进行归并排序。
代码实现
public void MergeList_Sq(Sqlist LA,Sqlist LB,Sqlist LC){
LC.length = LA.length + LB.length;
LC.elem = new int [LC.length];
int pa = 0;
int pb = 0;
int pc = 0;
int pa_last = LA.length - 1;
int pb_last = LB.length - 1;
while (pa <= pa_last&&pb <= pb_last){
if(LA.elem[pa] <= LB.elem[pb]) LC.elem[pc++] = LA.elem[pa++];
else LC.elem[pc++] = LA.elem[pb++];
}
while (pa <= pa_last)LC.elem[pc++] = LA.elem[pa++];
while (pb <= pb_last)LC.elem[pc++] = LA.elem[pb++];
}
算法的时间空间复杂度都为
利用链表实现上述操作的话,不需要开辟新的空间,可以使空间复杂度为1。
代码实现:
public void MergeList_L(Node LA,Node LB,Node LC){
Node pa = LA.next;
Node pb = LB.next;
LC = LA;
Node pc = LC.next;
while (pa != null&&pb != null){
if(pa.data <= pb.data){
pc.next = pa;
pc = pa;
pa = pa.next;
}
else if (pa.data > pb.data){
pc.next = pb;
pc = pb;
pb = pb.next;
}
}
pc.next=pa?pa:pb;
}