540 likes | 684 Views
Linear List. 第2章 线性表. 讲解纲要. 线性表的定义及其运算 线性表的存储结构: 顺序存储结构 链式存储结构 循环链表和双向链表 多项式相加问题. 线性表的定义. 线性表是 n(n ≥ 0) 个数据元素的有限序列 记作: ( a 1 ,a 2 ,…,a n ) a i ( 1≤i≤n ) 属于同一数据对象,具有相同的数据类型 元素之间的关系是线性逻辑关系: 表中有且仅有一个开始结点 除开始结点外,表中的每个结点均只有一个前驱结点 除终端结点外,表中的每个结点均只有一个后继结点.
E N D
Linear List 第2章 线性表
讲解纲要 • 线性表的定义及其运算 • 线性表的存储结构: • 顺序存储结构 • 链式存储结构 • 循环链表和双向链表 • 多项式相加问题
线性表的定义 • 线性表是n(n≥0)个数据元素的有限序列 记作:(a1,a2,…,an) ai(1≤i≤n)属于同一数据对象,具有相同的数据类型 • 元素之间的关系是线性逻辑关系: • 表中有且仅有一个开始结点 • 除开始结点外,表中的每个结点均只有一个前驱结点 • 除终端结点外,表中的每个结点均只有一个后继结点
线性表各种运算 (一) 基本运算: (1)求表长Length(L); (2)取第i个元素Get(L,i); (3)修改第i个元素Modify(L,i); (4)删除第i个元素Delete(L,i); (5)在第i个元素之后(前)插入一个新元素Insert(L,i,x); (6)按某种要求重排线性表中各元素的顺序 Sort(L,key); (7)按某个特定值查找线性表中的元素Index(L,x); (二) 复杂运算:合并、对有序表插入、删除等
顺序存储结构(向量) 链式存储结构 线性表常用的 两种存储结构
(一)顺序存储结构(向量) • 向量:内存中的一批地址连续的存储单元 • 线性表的顺序存储(向量存储) • 假设向量的第一个元素存放的地址为LOC(a1) • 每个元素ai占用的空间大小为L个字节 • 元素的存放地址为: LOC(ai)= LOC(a1)+L*(i-1) • 随机存储结构:只要确定了起始位置,其它任意元素都可随机存取
1、在结构体中使用静态一维数组 #define MAXSIZE 100 typedef int ElemType; typedef struct { ElemType elem[MAXSIZE]; // 数组域 int length; // 表长域 }Sqlist;
2、在结构体中使用动态一维数组 #define MAXSIZE 100 typedef int ElemType; typedef struct { ElemType *elem; // 数组域 int length; // 表长域 }Sqlist1; 例:Sqlist1 a; a.elem=(ElemType *)malloc(MAXSIZE*sizeof(ElemType)); free(a.elem);
插入运算 • 向量的插入操作: • 在线性表的第i-1和第i个数据元素之间插入一个新元素x • 线性表由(a1,…,ai-1,ai,…an)变为 (a1,…,ai-1,x,ai,…an) • 方法: • 将元素ai,…an向后各移一个位置 • 然后将x插在第i个位置上
算法2.1 void insert(Sqlist *v,int i,ElemType x) { if((i<0) || (i>v->length)) printf(“Error!”); else { // 向后移动数据元素 for(j=v->length-1;j>=i-1;j--) v->elem[j+1]=v->elem[j]; for(j=v->length-1;j>=i;j--) v->elem[j+1]=v->elem[j]; v->elem[j+1]=x; v->length=v->length+1; } } 假设在主函数中已建立线性表结构体s insert(&s,3,88);
删除运算 • 向量的删除操作: • 删除第i个元素ai • 线性表由(a1,…,ai-1,ai,ai+1, …an)变为 (a1,…,ai-1,ai+1,…an) • 方法: • 将元素ai+1,…an分别向前各移一个位置
算法2.2 void Delete(Sqlist *v,int i) { if((i<0) || (i>v->length-1)) printf(“Not Exist!”); else { // 向前移动数据元素 for(j=i;j<=v->length-1;j++) v->elem[j-1]=v->elem[j]; for(j=i;j<v->length-1;j++) v->elem[j]=v->elem[j+1]; v->length--; } }
插入运算和删除运算的复杂度分析: (1)移动元素次数的平均次数: • Ein= Ede= (2)在任何位置插入或删除元素的概率 • pi= qi= (3)则: • Ein= = • Ede= = 两个算法的复杂度均为O(n) pi是在第i个元素之前插入一个元素的概率; qi是删除第i个元素的概率;
其它运算——查找 • 查找: • 在线性表L中查找值为e的数据元素 • 若该元素存在,则返回它的位置 • 否则,返回-1表示不存在
算法2.3 int location(Sqlist L,ElemType e) { i=0; while(i<=L.length-1 && L.elem[i]!=e) i++; if(i<=L.length-1) return(i+1); else return(-1); }
其它运算——线性表的合并 • 合并: • 方法很多 • 假设两个线性表非递减有序(递增或等值),将其合并,合并后仍然保持非递减有序
算法2.4 void merge_list(Sqlist la,Sqlist lb,Sqlist *lc) { int i=0,j=0,k=0; lc->length=la.length+lb.length; while(i<=la.length-1 && j<=lb.length-1) { if(la.elem[i]<=lb.elem[j]) { lc->elem[k]=la.elem[i]; k++; i++; } else { lc->elem[k]=lb.elem[j]; k++; j++; } } while(i<=la.length-1) { lc->elem[k]=la.elem[i]; i++; k++; } while(i<=lb.length-1) { lc->elem[k]=lb.elem[i]; i++; k++; } }
(二)链式存储结构 • 顺序存储结构的缺点: • 频繁操作(插入、删除)时效率低下 • 存储空间必须预先分配,空间管理不灵活 • 线性表的链式存储: • 单链表 • 循环链表 • 双向链表
单链表 • 单链表:每个结点只有一个指针域 • 数据域 • 存储数据元素信息 • 指针域 • 存储直接后继结点的地址
附加头结点 • 其数据域闲置不用 • 几种情况: • 空表:附加头结点指针域为空,用∧表示 • 非空表:线性表的第一个数据元素a1放在附加头结点的下一个结点中 • 最后一个结点的指针域为空,也用∧表示 • 用指针变量h存放附加头结点的指针,即头指针h指向链表附加头结点的存储位置 • 对于链表的各项操作必须从头指针开始!
指针变量的主要操作 • 把结点归还,用函数free(p) • 结点p的数据域用p->data来表示 • 结点p的指针域用p->next来表示
单链表的基本运算 • 插入: (1)在p所指向的结点后插入一个元素x (2)在p所指向的结点前插入一个元素x (3)在值为x的元素前插入一个值为y的数据元素 (4)在链表的第i个结点处插入值为y的元素 • 删除: (1)删除p所指向结点的后继结点 (2)删除p所指向的结点 (3)删除值为x的数据元素 (4)删除链表第i个结点,返回该元素值
(1)在p所指向的结点后插入 { s=(Lnode *)malloc(sizeof(Lnode)); s->data=x; s->next=p->next;p->next=s; } (a)插入前 (b)插入后 (2) (1)
(2)在p所指向的结点前插入 { q=head; while(q->next!=p) q=q->next; // 先找到p之前的结点q s=(Lnode *)malloc(sizeof(Lnode)); s->data=x; s->next=p;q->next=s; }
(3)在值为x的元素前插入一个值为y的数据元素 算法2.5 void insert(Lnode *head,ElemType x,ElemType y) { s=(Lnode *)malloc(sizeof(Lnode)); s->data=y; q=head; p=q->next; while((p!=NULL) && (p->data!=x)) { q=p; p=p->next; } // 此时p指向值为x的结点,q指向值为x的结点的前一个结点 s->next=p;q->next=s; // 插入 }
(4)在链表的第i个元素结点处插入值为y的元素 算法2.6 void inserti(Lnode *head,int i,ElemType y) { int k=0;p=head; while((p!=NULL) && (k<i-1)) { p=p->next; k++; } if(k==i-1) // 找到第i-1个元素结点 { s=(Lnode *)malloc(sizeof(Lnode)); s->data=y; s->next=p->next; p->next=s; // 插入 } else printf(“\n i error!”); }
(1)删除p所指向结点的后继结点 { q=p->next; p->next=q->next; free(q); } 删除p所指向结点的后继结点
(2)删除p所指向的结点 { q=head; while(q->next!=p) q=q->next; //找到p的前驱 q->next=p->next; free(p); } 删除p所指向的结点
(3)删除值为x的数据元素 算法2.7 void Delete(Lnode *head,ElemType x) { q=head; p=q->next; while((p!=NULL) && (p->data!=x)) { q=p; p=p->next; } if(p==NULL) printf(“No!”); else { q->next=p->next; free(p); printf(“Yes!”); } }
(4)删除链表中第i个元素结点,返回该数据元素值(4)删除链表中第i个元素结点,返回该数据元素值 算法2.8 ElemType Deletei(Lnode *head,int i) { int k=0; p=head; while((p!=NULL) && (k<i-1)) { p=p->next; k++; } if((p!=NULL) && (k==i-1) { q=p->next; y=q->data; p->next=q->next; free(q); } else { printf(“\n i error!”); y=-1; } return y; }
单链表的其它运算——初始化空表 void Initiate(Lnode *head) { head->next=NULL; } void main() { Lnode *h; h=(Lnode *)malloc(sizeof(Lnode)); Initiate(h); … }
单链表的其它运算——单链表的建立 建立单链表: 从无到有建立一个链表: (1) 将线性表的数据元素一一输入,不断产生各结点; (2) 建立结点间的前后相链关系;
建立单链表的算法 算法2.10 void creat() { head=(Lnode *)malloc(sizeof(Lnode)); head->next=NULL; p=head; printf(“x=?”); scanf(“%d”,&x);//输入第一个元素 while(x!=-999) { ptr=(Lnode *)malloc(sizeof(Lnode)); ptr->data=x; ptr->next=NULL; p->next=ptr; p=ptr; printf(“x=?”); scanf(“%d”,&x); } // 输入下一个 }
单链表的其它运算——单链表的逆置 (1)最初状态 采用在附加头结点和第一个存放数据元素的结点之间不断插入后边元素结点的方法 (2)p=head->next (3)head->next=NULL
(4)while(p!=NULL) q=p->next p->next=head->next head->next=p p=q
(5)while(p!=NULL) q=p->next p->next=head->next head->next=p p=q
(6)while(p!=NULL) q=p->next=NULL; p->next=head->next; head->next=p p=q=NULL 结束
单链表的逆置算法 算法2.9 void nizhi(struct node *head) { p=head->next; head->next=NULL; while(p!=NULL) { q=p->next; p->next=head->next; head->next=p; p=q; } }
循环链表 • 将单链表的最后一个结点的指针指向单链表的表头 • 从表中任一结点出发可找到所有其它结点 • 除头指针h外,还加了一个尾指针r,尾指针r指向最后一个结点 (a)非空表 (b)空表
例:将两个循环链表首尾相接 语句段: { B->next=A->next; A->next=HB->next; free(HB); A=B; } 合并前: 合并后: 将两个循环链表合并时,只需改变两个指针值即可! ② ①
双向链表 • 每个结点除数据字段外,还包含两个指针: • 一个指向该结点的后继结点 • 另一个指向它的前趋结点 • 双向链表的好处: • 可从两个方向搜索某个结点 • 利用向前和向后链均可遍历整个链表
双向链表(续) • 空双向链表 • 非空双向链表
双向链表的操作 (1)在双向链表中的p结点前插入一个结点 假设:s是待插入的结点并已准备就绪 { s->next=p; s->prior=p->prior; p->prior->next=s; p->prior=s; } (2)维护原来的链关系 ④ ③ (1)处理新增加的链关系 ① ②
双向链表的操作(续) (2)在双向链表中删除结点p { p->prior->next=p->next; p->next->prior=p->prior; free(p); } ① ②
顺序存储结构与链式存储结构的比较 • 顺序方式 • 优点: • 内存存储密度高 • 结点等长时,可随机存取结点 • 缺点: • 插入、删除操作效率较低 • 需预先分配存储空间 • 链式 • 可克服顺序存储中的不足 • 适合运用于频繁插入和删除且存储空间大小不可预知的线性表
线性表处理——多项式相加问题 其中:p为n项多项式 aixi称为多项式的项(0≤i≤n) ai为系数,x为变量,i为指数 例如:设有两个已知多项式A和B 相加后将得到一个新多项式C:
多项式相加的链式存储结构 结点结构: typedef struct poly { int coef; // 系数 int exp; // 指数 struct poly *next; // 指向下一结点的指针 }Lpoly;