数据结构第二章笔记

第二章线性表本章的基本内容是:
线性表的逻辑结构
线性表的顺序存储及实现
线性表的链接存储及实现
顺序表和单链表的比较
线性表的其他存储及实现学生成绩登记表
学号 姓 名 数据结构 英语 高数
0101 丁一 78 96 87
0102 李二 90 87 78
0103 张三 86 67 86
0104 孙红 81 69 96
0105 王冬 74 87 66
职工工资登记表
职工号 姓 名 基本工资 岗位津贴 奖金
0101 丁一 278 600 200
0102 李二 190 300 100
0103 张三 186 300 100
0104 孙红 218 500 200
0105 王冬 190 300 100
数据元素之间的关系是什么?
线性表( Linear List )的定义
线性表:简称表,是n(n≥0)个具有相同类型的数据元素的有限序列。
线性表的长度:线性表中数据元素的个数。空表:长度等于零的线性表,记为:L=( )。
非空表记为:L=(a1, a2 , …, ai-1, ai , …, an)
其中,ai(1≤i≤n)称为数据元素;下角标 i 表示该元素在线性表中的位置或序号。
线性表的图形表示
线性表(a1, a2 , …, ai-1, ai , …, an)的图形表示如下:

2.1 线性表的逻辑结构线性表的特性
1.有限性:线性表中数据元素的个数是有穷的。
2.相同性:线性表中数据元素的类型是同一的。
3.顺序性:线性表中相邻的数据元素ai-1和ai之间存在
序偶关系(ai-1, ai),即ai-1是ai的前驱, ai是ai-1的后继; a1 无前驱,an无后继,其它每个元素有且仅有一个前驱和一个后继。

线性表的抽象数据类型定义
ADT List
Data
线性表中的数据元素具有相同类型,相邻元素具有前驱和后继关系
Operation
InitList
前置条件:表不存在输入:无功能:表的初始化输出:无后置条件:建一个空表
线性表的抽象数据类型定义
DestroyList
前置条件:表已存在输入:无功能:销毁表输出:无
后置条件:释放表所占用的存储空间
Length
前置条件:表已存在输入:无功能:求表的长度
输出:表中数据元素的个数后置条件:表不变
线性表的抽象数据类型定义
Get
前置条件:表已存在输入:元素的序号i
功能:在表中取序号为i的数据元素
输出:若i合法,返回序号为i的元素值,否则抛出异常后置条件:表不变
Locate
前置条件:表已存在输入:数据元素x 功能:在线性表中查找值等于x的元素输出:若查找成功,返回x在表中的序号,否则返回0 后置条件:表不变
线性表的抽象数据类型定义
Insert
前置条件:表已存在输入:插入i;待插x 功能:在表的第i个位置处插入一个新元素x 输出:若插入不成功,抛出异常
后置条件:若插入成功,表中增加一个新元素
Delete
前置条件:表已存在输入:删除位置i
功能:删除表中的第i个元素
输出:若删除成功,返回被删元素,否则抛出异常后置条件:若删除成功,表中减少一个元素
线性表的抽象数据类型定义
Empty
前置条件:表已存在输入:无功能:判断表是否为空输出:若是空表,返回1,否则返回0 后置条件:表不变
PrintList
前置条件:表已存在输入:无功能:遍历操作,按序号依次输出表中元素输出:表的各数据元素后置条件:表不变 endADT
进一步说明:
(1) 线性表的基本操作根据实际应用而定;
(2) 复杂的操作可以通过基本操作的组合来实
现;
– 假设利用两个线性表LA和LB分别表示两个集合A和 B(即:线性表中的数据元素即为集合中的成员),现要求一个新的集合A=AUB:
Get(one from B)-Locate(in A)-Insert(to A)?

(3) 对不同的应用,操作的接口可能不同。
– 删除表中的值为x的元素
– 删除表中的第i个元素

顺序表——线性表的顺序存储结构例:(34, 23, 67, 43)
34 23 67 43 4
用一段地址连续的存储单元存储要点依次存储线性表中的数据元素
顺序表——线性表的顺序存储结构例:(34, 23, 67, 43)
用什么属性来描述顺序表?
存储空间的起始位置
顺序表的容量(最大长度)
顺序表的当前长度
线性表的顺序存储结构——顺序表例:(34, 23, 67, 43)
34 23 67 43 4
如何实现顺序表的内存分配?顺序表 一维数组
顺序表
一般情况下,(a1,a2,…, ai-1,ai , …, an)的顺序存储:
0 … i-2 i-1 … n-1 Max-1

Loc(a1) Loc(ai)
如何求得任意元素的存储地址?
顺序表
一般情况下,(a1,a2,…, ai-1,ai , …, an)的顺序存储:
0 … i-2 i-1 … n-1 Max-1

Loc(a1) Loc(ai)
Loc(ai)=Loc(a1) + (i -1)×c
随机存取:在O(1)时间内存取数据元素存储结构和存取结构
存储结构是数据及其逻辑结构在计算机中的表示;存取结构是在一个数据结构上对查找操作的时间性能的一种描述。
“顺序表是一种随机存取的存储结构”的含义为:在顺序表这种存储结构上进行的查找操作,其时间性能为O(1)。
const int MaxSize=100; template //模板类 class SeqList
{
顺序表类的声明 T Get(int i); public:
SeqList( ) ; //构造函数 SeqList(T a[ ], int n); ~SeqList( ) ; //析构函数 int Length( ){return length;}
int Locate(T x );
void Insert(int i, T x); T Delete(int i);
void PrintList(); private:
T data[MaxSize];
int length;
};
顺序表的实现——无参构造函数
操作接口:SeqList( )

data	length

算法描述:
template SeqList:: SeqList( )
{ length=0;}
顺序表的实现——有参构造函数操作接口:SeqList(T a[ ], int n)

顺序表的实现——有参构造函数
算法描述:
template
SeqList::SeqList(T a[ ], int n)
{
if (n>MaxSize) throw “参数非法”; for (i=0; i length=n;
}
顺序表的实现——按位查找操作接口: T Get(int i)

算法描述:
template 时间复杂度?
T SeqList::Get( int i )
{ if (i>=1 && i<=length) return data[i-1]; }
顺序表的实现——按值查找操作接口: int Locate(T x )
例:在(35, 33, 12, 24, 42)中查找值为12的元素,返回在表中的序号。
注意序号和下标之间的关系

顺序表的实现——按值查找算法描述:
template
int SeqList::Locate(T x)
{ for (i=0; i if (data[i]==x) return i+1; return 0;
}
时间复杂度?
顺序表的实现——遍历算法描述:
template void SeqList::PrintList()
{ for (i=0; i cout< 时间复杂度?
顺序表的实现——插入
操作接口: void Insert(int i, T x) 插入前:(a1, … , ai-1, ai, … , an) 插入后:(a1, … , ai-1, x , ai, … , an)
ai-1和ai之间的逻辑关系发生了变化

顺序存储要求存储位置反映逻辑关系

存储位置要反映这个变化
顺序表的实现——插入
例:(35,12,24,42),在i=2的位置上插入33。
0 1 2 3 4
a1
35 a2
1233 a3
1224 a4
2442 42 4
5

33
什么时候不能插入? 注意边界条件
表满:length>=MaxSize
合理的插入位置:1≤i≤length+1(i指的是元素的序号)
顺序表的实现——插入算法描述——伪代码

  1. 如果表满了,则抛出上溢异常;
  2. 如果元素的插入位置不合理,则抛出位置异常;
  3. 将最后一个元素至第i个元素分别向后移动一个位置;
  4. 将元素x填入位置i处;
  5. 表长加1;
    顺序表的实现——插入
    算法描述——C++描述 基本语句?
    template void SeqList::Insert(int i, T x)
    {
    if (length>=MaxSize) throw “上溢”; if (i<1 | | i>length+1) throw “位置”; for (j=length; j>=i; j–) data[j]=data[j-1];
    data[i-1]=x; length++;
    }
    顺序表的实现——插入
    时间性能分析
    最好情况( i=n+1):
    基本语句执行0次,时间复杂度为O(1)。最坏情况( i=1):基本语句执行n次,时间复杂度为O(n)。
    顺序表的实现——插入
    时间性能分析
    平均情况(1≤i≤n+1):
    n+1 n 
    顺序表的实现——删 除操作接口: T Delete(int i) 删除前:(a1, …, ai-1,ai,ai+1,…,an) 删除后:(a1,…,ai-1,ai+1, …,an)
    ai-1和ai之间的逻辑关系发生了变化

顺序存储要求存储位置反映逻辑关系

存储位置要反映这个变化顺序表的实现——删除例:(35, 33, 12, 24, 42),删除i=2的数据元素。
0 1 2 3 4
a1
35 a2
1233 a3
2412 a4
2442 a5
42 5
4
仿照顺序表的插入操作,完成:

  1. 分析边界条件;
  2. 分别给出伪代码和C++描述的算法;
  3. 分析时间复杂度。
    顺序表的实现——删 除例:(35, 33, 12, 24, 42),删除i=2的数据元素。
    0 1 2 3 4
    a1
    35 a2
    1233 a3
    2412 a4
    2442 a5
    42 5
    4
    什么时候不能删除? 注意边界条件
    表空:length0
    合理的删除位置:1≤i≤length(i指的是元素的序号)
    顺序表的实现——删除
    算法描述——C++描述 基本语句?
    template T SeqList::Delete(int i)
    {
    if (length
    MaxSize) throw “下溢"; if (i<1 | | i>length) throw “位置”;
    x=data[i-1];
    for (j=i;; j length–; return x;
    }
    顺序表的实现——删除
    时间性能分析
    最好情况( i=n):
    基本语句执行0次,时间复杂度为O(1)。
    最坏情况( i=1):基本语句执行n-1次,时间复杂度为O(n)。
    顺序表的实现——删除
    时间性能分析平均情况(1≤i≤n): 
    顺序表的优缺点
    顺序表的优点:
    ⑴无需为表示表中元素之间的逻辑关系而增加额外的存储空间;
    ⑵随机存取:可以快速地存取表中任一位置的元素。
    顺序表的缺点:
    ⑴插入和删除操作需要移动大量元素;
    ⑵表的容量难以确定,表的容量难以扩充;
    ⑶造成存储空间的碎片。

单链表
顺序表 静态存储分配 事先确定容量
链 表 动态存储分配 运行时分配空间
单链表:线性表的链接存储结构。
存储思想:用一组任意的存储单元存放线性表的元素。
连续不连续零散分布
单链表
例:(a1, a2 ,a3, a4)的存储示意图
存储特点:

  1. 逻辑次序和物理次序不一定相同。
    2.元素之间的逻辑关系用指针表示。
    单链表
    单链表是由若干结点构成的;单链表的结点只有一个指针域。
    单链表的结点结构:
    数据域 指针域 data next
    data:存储数据元素 next:存储指向后继结点的地址
    单链表
    data next
    单链表的结点结构:
    template
    struct Node 如何申请一个结点?
    {
    T data;
    Node *next;
    };
    单链表
    data next
    单链表的结点结构:
    template s=new Node ; struct Node
    {
    T data;
    Node *next;
    };
    Node *s, *first;
    data next
    单链表 如何引用数据元素?
    *s.data ; s=new Node ;
    s->data ;
    如何引用指针域? s->next;

a2
0325
a1
0200

a4


a3
0300

单链表 …
0200 什么是存储结构?
0208 重点在数据元素之间的逻辑关系的
表示,所以,将实际存储地址抽象。
空表 0300 first=NULL
非空表 0325 first

a2
0325
a1
0200

a4


a3
0300

单链表 …
0200
头指针:存储第一个结点的地址,
0208 即,指向第一个结点。
尾标志:终端结点的指针域为空。
空表 0300 first=NULL
非空表 0325 first

a2
0325
a1
0200

a4


a3
0300

单链表 …
0200 空表和非空表不统一,缺点?
如何将空表与非空表统一?0208
空表 0300 first=NULL
非空表 0325 first

单链表
头结点:在单链表的第一个元素结点之前附设一个类型相同的结点,以便空表和非空表处理统一。
空表 first
非空表 first
template
class LinkList
{
单链表类的声明 ~LinkList( ); public: LinkList( );
LinkList(T a[ ], int n);
int Length( ); T Get(int i); int Locate(T x); void Insert(int i, T x);
T Delete(int i); void PrintList( );
private:
Node *first; };
单链表的实现——遍历
操作接口: void PrintList();
an ∧
p p
核心操作(关键操作):工作指针后移。从头结点(或开始结点)出发,通过工作指针的反复后移而将整个单链表“审视”一遍的方法称为扫描(或遍历)。
单链表的实现——遍历
操作接口: void PrintList();
an ∧
p p
template
int LinkList:: PrintList()
{ p=first->next; while (p!=NULL)
{ coutnext;
}
}
单链表的实现——求线性表的长度操作接口: int Length();
an ∧
p p
template int LinkList:: Length()
{
p=first->next; count=0; while (p!=NULL)
{ count++;
p=p->next;
} return count;
}
单链表的实现——按位查找操作接口: T Get(int i);
an ∧
p p
查找成功 查找失败
核心操作(关键操作):工作指针后移——遍历
单链表的实现——按位查找算法描述——伪代码

  1. 工作指针p初始化; 累加器j初始化;

  2. 循环直到p为空或p指向第i个结点
    2.1 工作指针p后移;
    2.2 累加器j加1;

  3. 若p为空,则第i个元素不存在,抛出查找位置异常;否则查找成功,返回结点p的数据元素;
    单链表的实现——按位查找算法描述——C++描述
    template T LinkList::Get(int i)
    { p=first->next; j=1; while (p!=NULL && j { p=p->next;
    j++;
    }
    if (p==NULL) throw “位置”; else return p->data;
    }

  4. 3 线性表的链接存储结构及实现
    单链表的实现——按值查找操作接口: int Locate(T x )
    an ∧
    p p
    查找成功 查找失败

  5. 3 线性表的链接存储结构及实现
    单链表的实现——按值查找
    算法描述:
    template
    int LinkList::Locate(T x)
    { p=first->next; j=1; while §{
    if(p->data==x) return j; else
    { p=p->next; j++; }
    } return 0;
    }
    单链表的实现———插入
    操作接口: void Insert(int i, T x);
    如何实现结点ai-1、x和ai之间逻辑关系的变化?

an ∧

算法描述:
s=new Node; s->data=x; s->next=p->next; p->next=s;

单链表的实现———插入注意分析边界情况——表头、表尾。
算法描述: 由于单链表带头结点, s=new Node; s->data=x; 表头、表中、表尾三种 s->next=p->next; p->next=s; 情况的操作语句一致。
单链表的实现———插入算法描述——伪代码

  1. 工作指针p初始化;累加器j清零;
  2. 查找第i-1个结点并使工作指针p指向该结点;
  3. 若查找不成功,说明插入位置不合理,抛出插入位置异常;否则,
    3.1 生成一个元素值为x的新结点s;
    3.2 将新结点s插入到结点p之后;
    单链表的实现———插入算法描述——C++描述 template void LinkList::Insert(int i, T x)if (p==NULL) throw “位置”;
    { else {
    p=first ; j=0; s=new Node;
    while (p!=NULL && jdata=x;
    {s->next=p->next; p=p->next; p->next=s; j++;}
    },}
    基本语句?时间复杂度?
    单链表的实现———构造函数
    操作接口:LinkList() ——无参构造函数初始化 算法描述:
    first=new Node;
    first ∧ first->next=NULL;
    template LinkList:: LinkList()
    { first=new Node; first->next=NULL;
    }
    单链表的实现———构造函数操作接口:LinkList(T a[ ], int n)
    头插法:将待插入结点插在头结点的后面。
    数组a 35 12 24 33 42
    初始化 first 算法描述:
    first=new Node; first->next=NULL;
    单链表的实现———构造函数操作接口:LinkList(T a[ ], int n) 头插法:将待插入结点插在头结点的后面。数组a 35 12 24 33 42
    插入第一个元素结点 算法描述:
    firsts=new Node;
    s->data=a[0]; s->next=first->next; first->next=s; 单链表的实现———构造函数操作接口:LinkList(T a[ ], int n) 头插法:将待插入结点插在头结点的后面。数组a 35 12 24 33 42
    依次插入每一个结点 算法描述:
    firsts=new Node;
    s->data=a[1]; s->next=first->next; first->next=s;
    单链表的实现———构造函数
    算法描述:
    template
    LinkList:: LinkList(T a[ ], int n)
    {
    first=new Node; first->next=NULL; for (i=0; i {
    s=new Node;
    s->data=a[i]; s->next=first->next; first->next=s;
    }
    }
    单链表的实现———构造函数操作接口:LinkList(T a[ ], int n) 尾插法:将待插入结点插在终端结点的后面。
    数组a 35 12 24 33 42
    初始化 first 算法描述:
    first=new Node; rear=first;
    rear
    单链表的实现———构造函数操作接口:LinkList(T a[ ], int n) 尾插法:将待插入结点插在终端结点的后面。
    数组a 35 12 24 33 42
    插入第一个元素结点 算法描述:
    s=new Node;
    first
    35 s->data=a[0]; rear rear rear->next=s;
    rear=s;
    单链表的实现———构造函数操作接口:LinkList(T a[ ], int n) 尾插法:将待插入结点插在终端结点的后面。数组a 35 12 24 33 42
    依次插入每一个结点 算法描述:
    firsts=new Node;
    35 12 s->data=a[1]; rear rear rear rear->next=s;
    rear=s;
    单链表的实现———构造函数操作接口:LinkList(T a[ ], int n) 尾插法:将待插入结点插在终端结点的后面。数组a 35 12 24 33 42
    算法描述:
    最后一个结点插入后
    rear->next=NULL;
    first
    35 42
    rear rear rear
    单链表的实现———构造函数
    算法描述:
    template
    LinkList:: LinkList(T a[ ], int n)
    { first=new Node ; rear=first; for (i=0; i { s=new Node ; s->data=a[i]; rear->next=s; rear=s; }
    rear->next=NULL;
    }
    单链表的实现———删除操作接口: T Delete(int i);
    如何实现结点ai-1和ai之间逻辑关系的变化?

算法描述:
q=p->next; x=q->data; p->next=q->next; delete q; 单链表的实现———删除注意分析边界情况——表头、表尾。
q
p->next=NULL
算法描述: 表尾的特殊情况:
q=p->next; x=q->data; 虽然被删结点不存在, p->next=q->next; delete q; 但其前驱结点却存在。
单链表的实现———删除算法描述——伪代码
1.工作指针p初始化;累加器j清零;
2. 查找第i-1个结点并使工作指针p指向该结点;
3. 若p不存在或p不存在后继结点,则抛出位置异常;否则,
3.1 暂存被删结点和被删元素值;
3.2 摘链,将结点p的后继结点从链表上摘下;
3.3 释放被删结点;
3.4 返回被删元素值;

单链表的实现———删除算法描述——C++描述 template T LinkList::Delete(int i)
{
p=first ; j=0; while (p && j { p=p->next; j++;
}
if (p==NULL | | p->next ==NULL ) throw “位置”;
else {
q=p->next; x=q->data; p->next=q->next; delete q; return x;
}
}

单链表的实现——析构函数操作接口:~LinkList( );
析构函数将单链表中所有结点的存储空间释放。

an ∧

算法描述: 注意:保证链表未处理的部分不断开
q=p;
p=p->next;
Delete q;
单链表的实现——析构函数
算法描述:
template LinkList:: ~LinkList( )
{
p=first;
while (p!=NULL)
{ q=p;
p=p->next; delete q;
}
}

2.3 线性表的链接存储结构及实现
循环链表

first
从单链表中某结点p出发如何找到其前驱?
将单链表的首尾相接,将终端结点的指针域由空指针改为指向头结点,构成单循环链表,简称循环链表。
循环链表
空表和非空表的处理一致 附设头结点
空循环链表
first
非空循环链表

first
循环链表——插入

算法描述:
s=new Node; s->data=x; s->next=p->next; p->next=s;
循环链表——插入
template void LinkList::Insert(int i, T x)if (j { else {
p=first ; j=0; //工作指针p初始化s=new Node;
while (p->next!=first && jdata=x;
{s->next=p->next; p=p->next; //工作指针p后移p->next=s;
j++;}
}}
与单链表的插入操作相比,差别是什么?
循环链表循环链表中没有明显的尾端 如何避免死循环循环条件:
p!=NULLp!=first p->next!=NULLp->next!=first
循环链表

first
如何查找开始结点和终端结点?
开始结点:first->next
终端结点:将单链表扫描一遍,时间为O(n) 循环链表带尾指针的循环链表

开始结点:rear->next->next rear 终端结点:rear
一个存储结构设计得是否合理,取决于基于该存储结构的运算是否方便,时间性能是否提高。
双链表

如何求结点p的直接前驱,时间性能?
为什么可以快速求得结点p的后继?
如何快速求得结点p的前驱?

双链表
双链表:在单链表的每个结点中再设置一个指向其前驱结点的指针域。
结点结构:
prior data next
data:数据域,存储数据元素; prior:指针域,存储该结点的前趋结点地址; next:指针域,存储该结点的后继结点地址。
双链表
prior data next
定义结点结构:
template
struct DulNode
{
T data;
DulNode *prior, *next;
};
启示? 时空权衡——空间换取时间
双链表的操作——插入
操作接口: void Insert(DulNode *p, T x);

s->prior=p;
s->next=p->next;
注意指针修改的相对顺序 p->next->prior=s; p->next=s;
双链表的操作——插入
特殊情况:在空表中插入一个结点(在表尾插入一个结点) first
空的双向链表 s first
非空的双向链表
s->prior=p; s->next=p->next; p->next->prior=s; 统一插入操作:
s->prior=p; s->next=p->next; if(p->next!=NULL)
p->next=s; p->next->prior=s; p->next=s;
双链表的操作——删除
操作接口: T Delete(DulNode *p);

(p->prior)->next=p->next;
(p->next)->prior=p->prior;
结点p的指针是否需要修改? delete p;
双链表的操作——删除
特殊情况:删除表尾结点
first

(p->prior)->next=p->next;
(p->next)->prior=p->prior; 统一删除操作:
(p->prior)->next=p->next;
delete p; if(p->next!=NULL)
(p->next)->prior=p->prior; delete p;
双链表类的定义
template
class DoubleLink
template { struct DulNode private:
{ DulNode *first; T data; public:
DulNode *prior; DoubleLink() ;
DulNode *next; ~DoubleLink();
}; void Append(T data);
void Display();
void Insert(int locate , T data);
T Get(int locate);
T Delete(int locate); };
双链表的构造-空表的构造 DoubleLink() ;
template
DoubleLink ::DoubleLink(){ first =new DulNode; first ->prior=NULL; first ->next=NULL;
}
追加(头插):void Append(T data);
template
void DoubleLink::Append(T data){ DulNode *s; s=new DulNode;
if (s!=NULL) { s->data=data; s->next= first ->next;
first ->next=s;
s->prior= first;
if (s->next!=NULL) s->next->prior=s;
}
else throw “内存空间不足”;
return;
}
遍历 void Display();
template void DoubleLink::Display(){ DulNode *p; p=first->next;
while(p !=NULL) {
cout p=p->next;
} cout< }

2.3 线性表的链接存储结构及实现
析构 ~DoubleLink();
template
DoubleLink::~DoubleLink()
{
DulNode *p,*q; p=first;
while(p!=NULL)
{ q=p->next;
delete p;
p=q;
}
}
顺序表和链表的比较存储分配方式比较
顺序表采用顺序存储结构,即用一段地址连续的存储单元依次存储线性表的数据元素,数据元素之间的逻辑关系通过存储位置来实现。
单链表采用链接存储结构,即用一组任意的存储单元存放线性表的元素。用指针来反映数据元素之间的逻辑关系。
时间性能比较
时间性能是指实现基于某种存储结构的基本操作(即算法)的时间复杂度。
按位查找:
顺序表的时间为O(1),是随机存取; 单链表的时间为O(n),是顺序存取。
插入和删除:
顺序表需移动表长一半的元素,时间为O(n);
单链表不需要移动元素,在给出某个合适位置的指针后,插入和删除操作所需的时间仅为O(1)。

空间性能比较
空间性能是指某种存储结构所占用的存储空间的大小。
定义结点的存储密度:
数据域占用的存储量存储密度=
整个结点占用的存储量
单链表中,如果数据域是整型变量int 则单链表结点的存储密度是:
= 50 %
空间性能比较
结点的存储密度:
顺序表中每个结点的存储密度为1(只存储数据元素),没有
浪费空间;
单链表的每个结点的存储密度<1(包括数据域和指针域),有指针的结构性开销。
整体结构:
顺序表需要预分配存储空间,如果预分配得过大,造成浪费,若估计得过小,又将发生上溢;
单链表不需要预分配空间,只要有内存空间可以分配,单链表中的元素个数就没有限制。
结论
⑴若线性表需:
•查找多,插入和删除少,或
•操作和元素在表中的位置密切相关宜采用顺序表作为存储结构;若线性表需频繁插入和删除时,则宜采用单链表做存储结构。
⑵若线性表中元素个数变化较大或未知时,最好使用单链表实现;如果事先知道线性表的大致长度,使用顺序表的空间效率会更高。
总之,线性表的顺序实现和链表实现各有其优缺点,不能笼统地说哪种实现更好,只能根据实际问题的具体需要,并对各方面的优缺点加以综合平衡,才能最终选定比较适宜的实现方法。

静态链表
静态链表:用数组来表示单链表,用数组元素的下标来模拟单链表的指针。
数组元素(结点)的构成:
数据域 指针域 data next
data:存储放数据元素; next:也称游标,存储该元素的后继在数组的下标。
静态链表
例:线性表(张,王,李,赵,吴)的静态链表存储
data next first
0
1first:静态链表头指针,为了方
2便插入和删除操作,通常静态链
3 李 表带头结点;
4 赵 avail:空闲链表头指针,空闲链
5 吴 表由于只在表头操作,所以不带 avail
6头结点;
7
8
静态链表
在线性表(张,王,李,赵,吴)中“王”之后插入“孙” data next data next
0 0
11
1
张 2
王 3
李 4
赵 5
吴 -1
7
8
-1
1
张 2
王 6
李 4
赵 5
吴 -1
孙 3
8
-1
2王之后 2

33 插入孙
44
55
6 6
77
88
静态链表
在线性表(张,王,李,赵,吴)中删除“赵” data next data next
0 0
11
1
张 2
王 3
李 4
赵 5
吴 -1
7
8
-1
1
张 2
王 3
李 5
赵 5
吴 -1
7
8
-1
22
3删除赵 3
4 4
5摘链 5
6 6
77
88
静态链表
在线性表(张,王,李,赵,吴)中删除“赵” data next data next
0 0
11
1
张 2
王 3
李 4
赵 5
吴 -1
7
8
-1
1
张 2
王 3
李 5
6
吴 -1
7
8
-1
22
3删除赵 3
4 4
5归还空间 5
66
77
88
静态链表
相对于顺序表而言,静态链表有什么优点?
优点:在执行插入和删除操作时,只需修改游标,不需要移动表中的元素,从而改进了在顺序表中插入和删除操作需要移动大量元素的缺点。
缺点:没有解决连续存储分配带来的表长难以确定的问题;静态链表还需要维护一个空闲链;静态链表不能随机存取。
间接寻址
间接寻址:是将数组和指针结合起来的一种方法,它将数组中存储数据元素的单元改为存储指向该元素的指针。

0 1 2 i-1 n-1 Max-1
插入操作移动的不是元素而是指向元素的指针。当元素占用的空间较多时,比顺序表的插入操作快得多。

2.6 应用举例
2.6.1 顺序表应用举例——大整数求和问题描述:
在用某种程序设计语言编程时,可能需要处理非常大或对运算
精度要求非常高的整数,这种大整数用该语言的基本数据类型无法表示。
例如,C++ 的长整型数的范围为
-2147483648~2147483647 超过该范围的整数无法表示怎么办?
借助顺序表解决。
Eg.9348594+993327765
清华大学出版社下标0
5 6 7 7 2 3 3 9 9

9 5 3 6 7 6 2 0 0 1
B9
C10

最低位(0)最高位

  1. 设计标志位flag(int,初值为0),用来记录计算过程中是否有进位
  2. 从数组低端开始,将对应位置上的两个数及flag的值相加,取结果的个位数,存入的c的对应位置上,将10位数上的值赋值给flag
  3. 重复进行计算,直到A或B计算完毕
  4. 处理A或B中剩余的部分
  5. 计算结果的位数
  6. 设计标志位flag(int,初值为0),用来记录计算过程中是否有进位
  7. 从数组低端开始,将对应位置上的两个数及flag的值相加,取结果的个位数,存入的c的对应位置上,将10位数上的值赋值给flag
  8. 重复进行计算,直到A或B计算完毕
  9. 处理A或B中剩余的部分
  10. 计算结果的位数
    SeqList Add(SeqList A,SeqListB){//友员函数实现 int flag=0,i=0,m,n; SeqList C; m=B.Length () ; n=A.Length() ; while(i C.data[i]=(A.data[i]+B.data [i]+flag)%10; flag=(A.data[i]+B.data [i]+flag)/10; i++;
    }

清华大学出版社1. 设计标志位flag(int,初值为0),用来记录计算过程中是否有进位数据结构(C++版)
2. 从数组低端开始,将对应位置上的两个数及flag的值相加,取结果的个位数,存入的c的对应位置上,将10位数上的值赋值给flag
3. 重复进行计算,直到A或B计算完毕
4. 处理A或B中剩余的部分 5. 计算结果的位数 for(;i C.data[i]=(A.data[i]+flag)%10;flag=(A.data[i]+flag)/10; }
for(;i C.data[i]=(B.data[i]+flag)%10; flag=(B.data[i]+flag)/10; }
int max; max=n; if(m>n) max=m;
C.length =max+flag; //如果最后一位产生进位
if(flag==1) C.data [C.length-1 ]=1; return C;
}

  1. 设计标志位flag(int,初值为0),用来记录计算过程中是否有进位
  2. 从数组低端开始,将对应位置上的两个数及flag的值相加,取结果的个位数,存入的c的对应位置上,将10位数上的值赋值给flag
  3. 重复进行计算,直到A或B计算完毕
  4. 处理A或B中剩余的部分
  5. 计算结果的位数
    SeqList Add1(SeqList A,SeqListB){ //非友员函数实现 int flag=0,i=1,m,n; SeqList C; m=B.Length () ;
    n=A.Length() ;
    while(i<=n && i<=m) {
    C.Insert(i,(A.Get(i)+B.Get(i)+flag)%10); flag=(A.Get(i)+B.Get(i)+flag)/10; i++;
    }
    清华大学出版社1. 设计标志位flag(int,初值为0),用来记录计算过程中是否有进位数据结构(C++版)
  6. 从数组低端开始,将对应位置上的两个数及flag的值相加,取结果的个位数,存入的c的对应位置上,将10位数上的值赋值给flag
  7. 重复进行计算,直到A或B计算完毕
  8. 处理A或B中剩余的部分
  9. 计算结果的位数
    for(;i<=n;i++) {
    C.Insert(i,(A.Get(i)+flag)%10);flag=(A.Get(i)+flag)/10; }
    for(;i<=m;i++){
    C.Insert(i,(B.Get(i)+flag)%10); flag=(B.Get(i)+flag)/10; }
    int max; max=n;
    if(m>n) max=m;
    if(flag==1) C.Insert(max+flag,1); return C;
    }

2.6 应用举例
2.6.2 单链表的应用举例——一元多项式求和在数学上,一个一元多项式Pn(x)可按升幂的形式写成:
P X P PX PX PXn( )= 0 + 1 + 2 2 + 3 3 ++PXn n
它实际上可以由n+1个系数唯一确定。因此,在计算机内,可以用一个线性表P来表示:
P P P P= ( 0, 1, 2,,Pn)
假设Qm(x)是一个一元多项式,则它也可以用一个线性
表Q来表示,即
Q q q q= ( 0, 1, 2,,qm)
若 假 设 m Rn(x)=Pn(x)+Qm(x),也可以用线性表R来表示:
R p q p q p q= ( 0 + 0, 1 + 1, 2 + 2,, p q pm + m, m+1,, pn)
可以采用顺序存储结构来实现顺序表的方法,使得多项式的相加的算法定义十分简单,即p[0]存系数p0,p[1]存系数 p1, …, p[n]存系数p n,对应单元的内容相加即可。
但是在通常的应用中,多项式的指数有时可能会很高并且变化
很大。例如:
R x( )=1+5x10000 +7x20000
•若采用顺序存储,则需要20001个空间,而存储的有用数据只有三个,这无疑是一种浪费。
•若只存储非零系数项,则必须存储相应的指数信息。假设一元多项式Pn(x)=p1xe1+p2xe2+…+pmxem,其中pi是指数为ei的项的系数(且0≤e1≤e2≤…≤em=n), 若只存非零系数,则多项式中每一项由两项构成(指数项和系数项),用线性表来表示,即 
((p e1 1, ),(p e2 2, ),,(p em m, ))
采用这样的方法存储,在最坏情况下,即n+1个系数都不为零, 则比只存储系数的方法多存储1倍的数据。对于非零系数多的多项式则不宜采用这种表示。
存储结构的选择
• 顺序存储
• 顺序存储的问题分析
• 链式存储
建立一元多项式的链式存储
(1)用单链表存储多项式的结点结构如下:
  struct Polynode  { 
Node data;  
Polynode *next;   struct Node
{ int coef;
int exp;
};
}; coef exp next
data next
一元多项式单链表的结点结构:
多项式的相加多项式相加的过程:

  1. 两个多项式中所有指数相同的项的对应系数相加,
    若和不为零,
    则构成“和多项式”中的一项;
  2. 所有指数不相同的项
    均复抄到“和多项式”中。
    多项式的相加
    AH = 1 - 3x6 + 7x12
    BH = - x4 + 3x6 - 9x10 14 
    pc pa
    AH.first
    BH.first
    CH.first

pc
AH.first

CH.first

CH.first

CH.first

CH.first AH.first7 12 ∧ pc
-9 10 8 14∧
pb
CH.first
CH.first

CH.first

CH.first
一元多项式相加中的三种情况
1 若pa->expexp,则结点pa所指的结点应是“和多项式”中的一项,令指针pa、pc后移;
2 若pa->exp>pb->exp,则结点pb所指的结点应是“和多项式”中的一项, 将结点pb插入在结点pa之前,且令指针 pa在原来的链表上,pb 、pc后移; 
3 若pa->exp=pb->exp, 则将两个结点中的系数相加, 释放pb结点;
1 当和不为零时修改结点pa的系数域, pa、pc后移;
2 若和为零,释放pa,pa后移。

pa,pb:当前比较的结点数据结构(C++版)
算法 pc:刚刚插入c的结点, p:被删结点

  1. 设置四个工作指针,pa, pb, pc, p,并对其进行初始化
  2. 当 pa!=NULL 并且pb!=NULL时,重复做以下工作
  3. if(pa.exp
  4. if(pa.exp>pb.exp),将pb加入到C中,后移pb、pc;
  5. if(pa.exp==pb.exp) ,pa、pb的系数相加,删除pb,pb 后移
  6. 如果和为0,删除pa, pa后移
  7. 否则,将和写入到pa中,将pa加入到C中,pa、 pc后移;
  8. 如pa!=NULL,将pa链入C中,否则将pb链入C中;
    核心代码
    Void Add(LinkList &A, LinkList B){
    Node *pa, *pb, *pc, *p;
    T a, b; //存放pa、pb两个指针变量中存储的系数 pc = A.GetHead(); //结果存放指针
    p = B.GetHead();
    pa = A.GetHead()->next; //pa指向ah pb = B.GetHead()->next; //pb指向bh
    delete p; //删去bh的表头结点
    struct Node   struct Polynode  
    { { 
    int coef; Node data;  int exp; Polynode *next;  
    }; };
    while ( pa != NULL && pb != NULL ) {
    a = pa->data; b = pb->data;
    if(a.exp==b.exp){ //pa->exp == pb->exp
    a.coef = a.coef + b.coef; //系数相加
    p = pb; pb = pb->next;
    delete p; //删去原pb所指结点
    if (a.coef == 0) {//相加为零, 该项删除 p = pa; pa = pa->next; delete p;
    } else { //相加不为零, 加入c链
    pa->data = a; pc->next = pa; pc = pa; pa = pa->next;
    }
    }
    else if(a.exp>b.exp) { //pa->exp > pb->exp pc->next = pb; pc = pb; pb = pb->next;
    }
    else{ //pa->exp < pb->exp pc->next=pa; pc = pa; pa = pa->next;
    }
    }
    if (pa!=NULL ) pc->next=pa; //处理剩余部分
    else pc->next=pb;
    }
    • 对结果表达式的构造:
    – 从空表开始,依次将A或B中结点加入到C中,完成
    C表的构造
    • 教材中代码:
    – 将A看作是结果表达式的一部分,将B中的节点依次插入到A中,完成结果表达式的构造
    – 存在的问题
    • 未对指向节点前驱的指针进行及时的修改
    不带头结点的单链表逆转

A NULL
设置三个辅助变量
p:指向链表的头结点,头指针,初值为list.head q:指向p的后继结点,初值为NULL,
front: 逆转之后的链表的头指针,初值为NULL。
不带头结点的单链表逆转算法描述
„ 如果链表为空表,抛出异常
„ 设置三个辅助变量p(指向链表的头结点,头指针,初值为
list.head),q(指向p的后继结点,初值为null),front(逆转之后的链表的头指针,初值为null)。
„ 在链表不空的情况下,重复做以下工作:
用q记录p的后继(原链表的新的头结点)
„ p.next=front; //将p作为头结点,链入逆转之后的表中
分别修改两个链表的头指针
public void reverse() //将单链表逆转
{
Node *p= a->head, q=null, front=NULL; while (p!=NULL)
p = q;

建立排序的单链表
构造链表,使得在插入过程中,依次将要插入的数据同链表中的数据进行比较,确定插入点进行插入.
插入过程:
寻找比插入值(element)大的第一个节点,查找成功的情况下,进行插入。
如果查找失败,则新插入的节点为最后一个节点
first … b 带头结点的单链表
有序(升序)链表的插入
void LinkList::Insert(T x) while(p && x>p->data)
{ {
Node *s,*p,*front; front=p;
s=new Node; p=p->next; s->data=x; }
front=first; p=first->next; s->next=p; front->next=s; return ; }
first 1 2 … n
b 带头结点的单链表
建立有序链表
void LinkList::create(T a[],int n)
{
for(int i=0,i }
first …
b 带头结点的单链表
有序表的合并
• 有两个链表LA和LB,其元素均为非递减有序排列,编写一个算法,将它们合并成一个链表LC,要求LC也是非递减有序排列
• 算法思想:
• 设表LC是一个空表,为使LC也是非递减有序排列,可设两个指针pa、pb、 pc分别指向表LA、LB和LC中的元素,
• 若pa->data>pb->data,则先将pb插入到表LC中;
• 若pa->data<=pb->data ,则先将pa插入到表LC中,
• 如此进行下去,直到其中一个表被扫描完毕,然后再将未扫描完的表中剩余的所有元素放到表LC中。

合并算法

  1. 设置工作指针pa,pb,pc,并对其进行初始化
  2. 重复做以下工作,直到pa或pb为空
  3. 将pa的值和pb的值进行比较
  4. 如果pa->data>pb->data,
  5. pc->next=pb,pb=pb->next
  6. 如果pa->data<=pb->data,
  7. pc->next=pa,pa=pa->next
  8. Pc=pc->next
  9. 如果pa不空,
  10. 则将pa中剩余的结点链入pc
  11. 否则,将pb中剩余的结点链入pc
    LA…
    LB…
    template
    void merge(LinkList LA, LinkList LB){
    Node *pa,*pb,*pc;
    pa=LA.first; pb=LB.first; pc=LA.first; pa=pa->next; pb=pb->next;
    while(pa&&pb) {
    if (pa->data>pb->data)
    { pc->next=pb;pc=pb;pb=pb->next;} else
    { pc->next=pa; pc=pa;pa=pa->next; }
    }
    if (pa) pc->next=pa;
    else pc->next=pb;
    }
    本章总结

上机实验
P169 顺序表操作验证
P174 单链表操作验证(循环链表,双链表)设计:
分别用顺序表和单链表通过基本操作的组合来实现:假设利用两个线性表LA和LB分别表示两个集合A和
B(即:线性表中的数据元素即为集合中的成员),现要求修改集合A,使得A=A∪B ,即集合A存储A和B的并集。
150

你可能感兴趣的:(数据结构第二章笔记)