1.线性结构特点:
1.只有一个首节点和尾节点
2.除了首位节点之外,其他节点只有一个直接前驱和直接后继
线性结构反映节点间的逻辑关系是一对一的
线性表的直接定义:用数据元素的有限序列表示
eg:(a1,a2,,,,,,,ai-1,ai,ai+1,,,,,,,an)
a1处:线性起点 ai-1是ai的直接前驱 ai+1是ai的直接后继
下标是元素的符号,表示元素在表中的位置
n=0时称为空表,记做L=() n为线性表的长度
同一线性表中的元素必定具有相同的个性
1.1判断题
1.数据逻辑结构是指数据元素之间的逻辑关系 对
2.线性表的逻辑结构定义是唯一的,不依赖于计算机 对·
3.线性表反映节点间的逻辑关系是一对一的 对
4.一维向量是线性表,但是二维或者n维数组不是 错
错
1.2编程题
假设有两个集合A和B,分别用两个线性表LA和LB表示,即:线性表中的元素即为集合中的成员。现在要求一个新的集合A=A并B
思路:
步骤:
1.取得线性表B中的一个元素: getElem(LA,i,e) 即将下标为i的元素赋值给e。
可循坏执行此函数n(LB长度)次。
2.判断此元素在LA中是否存在。locateElem(LA,e,equal());
3.如果e不存在,则插入之。
listInsert(LA,n+1,e);(n表示线性表LA当前的长度)
可大概得出代码段为:
1.3例三
1设置两个指针指向LA和LB中的数据9元素
getElem(LA,i,ai);
getElem(LB,i,bi);
2.将其中较小的数据元素插入到LC中,直至一个表为空
insertElem(LC,++k,e);
对应的i或者j也要加一
3.将剩下的一个表中的元素以此插入。
先getElem然后再listInsert(LC,++k,e);
2.2线性表的顺序表示及实现
2.2.1顺序表的表示
线性表的顺序表表示又称为顺序存储结构或者顺序映像
顺序存储的定义:将逻辑上相邻的数据元素存储于物理上相邻的存储单元中
顺序存储方法:用一组地址相邻的存储存储单元以此存储线性表的元素,可以通过数组V[n]来 实现。
顺序表的特点:
1.逻辑上相邻的数据元素,其物理上也相邻
2.若以知表中首元素在存储器中的位置,则其他元素的位置也可以求出
利用:
可以求出。
n计算机内存的存取原理是:如果知道该内存单元的地址,马上就可以读取到里面的内容,速度非常快,称为随机存取。
2.2.2顺序表的实现
# define LIST_INIT_SIZE 100 //符号常量 // 线性表存储空间的初始分配量
# define LISTINCREMENT 10 // 线性表存储空间的分配增量
typedef struct { //typedef 给结构类型起别名
ElemType *elem; //表基址
int length; //表长(特指元素个数)
int listsize; //表当前存储容量
}SqList
线性表的初始化操作:
Status InitList_Sq(SqList &L){
//构造一个空的线性表L
L.elem=(ElemType* )malloc(LIST_INIT_SIZE*sizeof(ElemType)); //为数据元素开辟一维数组空间
if (!L.elem) exit(OVERFLOW); //存储分配失败
L.length=0; // 空表长度为0
L.listsize=LIST_INIT_SIZE; //初始存储容量
return OK;
}
线性表的销毁操作:
Status DestroyList(SqList &L)
{
free(L.elem);
L.elem=NULL;
L.length=0;
L.listsize=0;
return OK;
}
修改 通过数组的下标便可访问某个特定元素并修改之:
status modify(SqList &L, int i, ElemType e){ //注意:i是位置
L.elem[i-1]=e; //或者 *(L.elem+i-1)=e
return OK;
}
显然,顺序表元素修改操作的时间效率是T(n))=O(1)
在线性表的第i个位置插入一个元素:
1.判断插入位置i是否是1<=i<=L.length 合法
2.将第n至第i位的元素向后移一位
3.把要插入的元素赋值给第i个元素
4.表长加一
Status listInsert(Sqlist &L,int i,ElementType e){
ElementType *p;
p=&L.elem;
if(i>=1||i<=L.length)
return ERROR;
if(L.length>=L.listsize){
newbase=(ElemType )realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));
for(int a=L.length-1;a>=i;i--)
p[a+1]=p[a];
P[a]=e;
}
删除顺序表中第i个位置的元
和上一操作思路大致相同。
以上操作应用举例
void MergeList_Sq(SqList La,Sqlist Lb,Sqlist &Lc)
void MergeList(List La,List Lb,list &Lc)
{
InitList(Lc);
i=j=1;k=0;
La_len=Listlength(La);
Lb_len=Listlength(Lb);
while((i<=La_len)&&(j<=Lb_len))
{
GetElem(La,i,ai);GetElem(Lb,j,bj)
if(ai<=bj) {ListInSert(Lc,++k,ai) ;++i; }
else{ListInsert(Lc,++k,bj):++j;
}
while (i<=La_len)
{GetElem(La,i,ai);
ListInsert(Lc,++k,ai)}
}
while (j<=Lb_len)
{GetElem(Lb,j,bj);
ListInsert(Lc,++k,bj)}
}//MergeList
}
方法二:
Void MergeList_Sq(SqList La, SqList Lb, SqList &Lc)
{pa=La.elem; pb=Lb.elem;
Lc.listsize=Lc.length=La.length+Lb.length;
Pc=Lc.elem= =(ElemType *)malloc(Lc.listsize*sizeof(ElemType));
If(!Lc.elem)exit(OVERFLOW);
pa_last=La.elem+La.length-1;
pb_last=Lb.elem+Lb.length-1;
While(pa<=pa_last && pb<=pb_last){
if (*pa<=*pb) *pc++=*pa++;
else *pc++=*pb++; }
While(pa<=pa_last){*pc++=*pa++; }
While(pb<=pb_last){*pc++=*pb++; } }
2.2.3顺序表的运算效率分析
插入算法的时间效率分析:
p(i)=1/(n+1) ,删除概率q(i)=1/n ,则:
因此,时间复杂度T(n)=O(n)
显然,顺序表的空间复杂度S(n)=O(1)
merge算法的时间效率分析:
费时在比较算法上面,插入算法由于一直插入至LC末尾,所以时间效率是不变的,根据代 码,总时间效率为T(n)=O(La.length+Lb.length);
3.小结:
顺序表(线性表的顺序存储结构)的特点:
逻辑关系上相邻的两个元素在物理存储位置上面也相邻
优点:可以随机的存取表中的任一元素
缺点:在插入删除某一元素时,需要移动大量的元素。
2.3线性表的链式表示和实现
2.3.1链表的表示
1.链式存储的特点:
逻辑上相邻,物理上不一定相邻
示意图
每个存储节点都包括两个部分:数据域和指针域(链域)
· 在单链表中,任一节点的存储位置都由该节点的直接前驱的指针域的值指示
2.与链式存储有关的术语
1)结点:数据元素的存储映像。由数据域和指针域两部分组成;
2)链表: n 个结点由指针链组成一个链表。它是线性表的链式像,
称为线的 链式存储结构。
3)单链表、双链表、多链表、循环链表:
结点只有一个指针域的链表,称为单链表或线性链表;
有两个指针域的链表,称为双链表;
有多个指针域的链表,称为多链表;
首尾相接的链表称为循环链表。
头指针,头结点,首元结点之间的关系
头指针:指向链表中第一个节点的指针
头结点:在首元结点之前附设的一个结点,数据域内只放空表标志或者表长等信息
首元结点:指链表中存储线性表第一个数据元素a1的节点
例题1:
解:
头指针中存储的是首元结点的地址:即ZHAO的地址31
注:
链式存储结构中不一定要有头指针
无头结点时,当头指针的值为空时表示空表;
有头结点时,当头结点的指针域为空时表示空表。
例题二:
头结点的数据域可以为空吗?
答:头结点的数据域可以为空,也可存放线性表长度等附加信息,但是此节点不能记入链表长度值。
例题三:
讨论3. 链表的数据元素有两个域,不再是简单数据类型,编程时该如何表示?
答:因每个结点至少有两个分量,所以要采用结构体数据类型。
Typedef struct Lnode {
ElemType data; //数据域
struct Lnode *next; //指针域
}Lnode, *LinkList; // LinkList为指向Lnode类型的指针
其中LinkList很重要 因为做辅助指针指向首元结点或者指向插入位置结点时都要用到LinkList
3.补充:结构数据类型及c语言表示法
2.3.2链表的实现
1.单链表的读取(或修改):
单链表中想取得第i个元素,必须从头指针出发寻找(顺藤摸瓜),不能随机存取 ,只能顺序存取,注意判断i不合法的情况,和表空的情况
思路:要修改第i个数据元素,关键是要先找到指向该结点的指针p,然后用p->data=new_value 即可。
Status GetElem_L(LinkList L, int i, ElemType &e){
// 注意:L为带头结点的单链表的头指针
// 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
P=L->next; j=1;
//j用来计数
while(p && j<i) {p=p->next; ++j;}
if (!p || j>i) return ERROR;
//!p即p为空,有两种情况,第一种是表为空;第二种是i的值超过了表的长度,while循环执行完后p走到最后一个元素还没有找到i的位置。j >i 就是i是0或者负数的情况。
e=p->data;
return OK;
}
2.单链表的插入:
1.使LinkList指针指向该插入位置的节点,即第i-1个结点。
2.元素x结点应预先生成:
S=(ElemType*)malloc(m);
S->data=x;
插入时原理示意图
插入核心语句:s->next=p->next; p->next=s;
代码:
Status ListInsert_L(LinkList &L,int i,ElemType e){
p=L; j=0;
while(p && j<i-1){p=p->next;++j}
//p将指向第i-1个元素
if(!p || j>i-1) return ERROR;
s=(LinkList)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
3.单链表的删除操作:
核心语句:
1.通过while循环使指针p指向第i-1个元素
2.q=p->next;
3.p->next=q->next
4.free(q);
讨论:在链表中设置头结点有什么好处?
头结点即在链表的首元结点之前附设的一个结点,该结点的数据域中不存储线性表的数据元素,其作用是为了对链表进行操作时,可以对空表、非空表的情况以及对首元结点进行统一处理,编程更方便。
4.单链表的建立和输出
实现思路:先开辟头指针,然后陆续为每个数据元素开辟存储空间并赋值,并及时将地址送给前面的指针。
实现方法:头插法和尾插法。
头插法:每次插入都插入到最前面。
Void CreateList_L (LinkList &L, int n) {
//逆位序输入n个元素的值,建立带表头结点的单链线性表L.
L = (LinkList) malloc (sizeof(LNode));
L→next = NULL; // 先建立一个带头结点的单链表
for ( i =n; i>0; --i) {
p = (LinkList) malloc (sizeof(LNode)); // 生成新结点
scanf (&p →data); // 输入元素值
p→next = L→next ;
L→next = p; // 插入到表头
}
} // CreateList_L
尾插法:每次插入都插入到尾部。
Void CreateList_L (LinkList &L, int n) {
//正序输入n个元素的值,建立带表头结点的单链线性表L.
L = (LinkList) malloc (sizeof(LNode));
L→next = NULL; // 先建立一个带头结点的单链表
q=L;//q总是指向最后一个元素
for ( i =n; i>0; --i) {
p = (LinkList) malloc (sizeof(LNode)); //新结点
scanf (&p →data); p->next=null; // 输入元素值
q→next = p; //把新结点p放到q的后面
q=q→next ;
}
} // CreateList_L
最后一段代码很重要:q=q->next;因为q指针必须时刻指向最后一个元素。
例子:尾指针插入法实现26字母链表。
void build(){
int i;
head=(LNode*)malloc(m); //head是指向第一个结点
p=head;
for(i=1;i<26;i++) {
p->data=i+‘a’-1;
q=(LNode *)malloc(m);//q是一个新的结点
p->next=q;
p=p->next; //p总是指向最后一个结点
//最后一个结点的data等待下次的输入
}
p->data=‘z’; p->next=NULL ;
}
遍历算法设计:
void travle(LinkList L){
LinkList p
cout<<"建立的链表为:";
for(p=L->next;p!=NULL;p=p->next)
cout<<p->data<<" ";
}
求单链表长度:
void len_LL (LinkList L,int &len){
len=0;
LinkList p;
for(p=L->next;p!=NULL;p=p->next)
len++;
}
应用:单链表逆置
void (LinkList L){
LinkList q;
for(q=L->next;q!=NULL;p=p->next)
listInsert(LinkList &L2,n);//头插法插入元素
}
两个链表的归并(类似于两个线性表归并的思路)
已知:线性表 A、B,分别由单链表 LA , LB 存储,其中数据元素按值非递减有序排列,
要求:将 A ,B 归并为一个新的线性表C , C 的数据元素仍按值非递减排列 。设线性表 C 由单链表 LC 存储。
假设:A=(3,5,8,11),B=(2,6,8,9,11)
预测:合并后 C =( 2 , 3 , 5 , 6 , 8 , 8 , 9 , 11,11 )
思路:
具体为三步:
搜索,比较,插入;
搜索:需要两个LinkList指针指向每一个元素
比较:比较上述指针指向的两个元素的数据域
插入:将数据域小的插入新的链表。
Void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc)
{ //按值排序的单链表LA,LB,归并为LC后也按值排序
pa=La->next; pb=Lb->next;
Lc=La;pc=La //用La的头结点,作为Lc的头结点
while(pa&&pb) //将pa 、pb结点按大小依次插入C中
{ if(pa->data<=pb->data)
{pc->next=pa; pc=pa; pa=pa->next;}
else {pc->next=pb; pc=pb; pb=pb->next}
}
pc->next = pa?pa:pb ; //插入剩余段
free(Lb); //释放Lb的头结点
} //MergeList_L
2.3.3其他形式的链表
讨论:用一位数组可以存放链表吗?
答:能。只要定义一个结构类型(含数据域和指示域)数组,就可以完全描述链表,这种链表称为静态链表。
注:数据域含义与前面相同,指示域相当于前面的指针域。
静态链表的结构:
#define MAXSIZE 1000
typedef struct{
ElemType data;
int cur;
}component,SLinkList[MAXSIZE]
讨论二:链表能不能首位相连:
答:能。只要将表中最后一个结点的指针域指向头结点即可 (P->next=head;) 。这种形成环路的链表称为循环链表。
空循环链表指针域指向头结点本身。
•循环链表的操作与线性链表基本一致,差别在于算法中的循环条件不是p或p->next是否为空,而是它们是否等于头指针。
双向链表:
空双向链表。
非空双向循环链表。
双向循环链表的插入和删除:
插入:
Status ListInsert_DuL(DuLinkList &L,int i, ElemType e){
If(!(p=GetElemP_DuL(L,i))) return ERROR; //检查i处是否有结点 并且顺便让p指向i处的结点
If(!(s=(DuLinkList)malloc(sizeof(DulNode)))) return ERROR; //为新节点提前分配空间
s->data=e; //新结点赋值
s->prior=p->prior; //s前指向第i个结点之前的
p->prior->next=s;//第i-1个结点指向s结点本身
s->next=p; //s结点指向p
p->prior=s;//p结点本身的前指针域指向s
return Ok;
}
删除:
双向链表的删除操作:
Status ListDelete_DuL(DuLinkList &L,int i, ElemType &e){
If(!(p=GetElemP_DuL(L,i))) return ERROR;
e=s->data;
p->prior->next=p->next;
p->next->prior= p->prior;
//两行的顺序是否可以颠倒? 可以。
free(p); return Ok;
}
2.3.4链表的运算效率的分析
时间效率分析:
1. 查找 因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为 O(n)。
2. 插入和删除 因线性链表不需要移动元素,在给出某个合适位置的指什后,插入和删除操作所需的时间仅为 O(1)。
但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为 O(n)。书中的两个算法的时间复杂度都为O(n)
空间效率分析:
链表中每个结点都要增加一个指针空间,相当于总共增加了n 个整型变量,空间复杂度为 O(n)。
3.本章小结
讨论1:线性表的逻辑结构特点是什么
答:只有一个首节点和尾节点,除了首尾节点之外每个节点只有一个直接前驱和直接后继。简言之:线性结构反映的节点之间的裸机价关系是一对一的。
顺序存储时,线性结构中逻辑结构相邻的数据元素物理存放地址也相邻。要求内存中可用存储单元的地址必须是连续的。
线性结构的链式存储中,相邻数据元素可以随意存放,但所占存储空间为两个部分,一部分存储节点值,另一部分存放表示节点之间关系的指针。
讨论2:优缺点:
顺序存储:存储密度大,空间利用率高,但是插入删除元素时不方便
链式存储:优点是插入或者删除元素时不方便,使用灵活,缺点是存储密度小,空间利用率低。讨讨论3:什么情况下用线性表比用链表要好?
顺序表适宜于做查找这样的静态操作;链表宜于做插入、删除这样的动态操作。
若线性表的长度变化不大,且其主要操作是查找,则采用顺序表;
若线性表的长度变化较大,且其主要操作是插入、删除操作,则采用链表。