目录
一.课本知识点
1.线性结构
- 定义:若结构是非空有限集,则有且仅有一个开始结点和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。
- 可表示为:(a1 , a2 , ……, an)是一个有序(次序)集 。
特点:
① 只有一个首结点和尾结点;
② 除首尾结点外,其他结点只有一个直接前驱和一个直接后继。
简言之,线性结构反映结点间的逻辑关系是 一对一 的。
2.线性表
-
定义: 用数据元素的有限序列表示
例子:
-
线性表的抽象数据类型定义:
-
线性表的类型定义:
例子:
//伪代码 void union(List &La, List Lb) { La_len = ListLength(La); // 求线性表La的长度 Lb_len = ListLength(Lb); // 求线性表Lb的长度 for(i=1;i<=Lb_len; i++) { GetElem(Lb, i, e); // 取Lb中第i个数据元素赋给e if (!LocateElem ( La, e, equal( )) ) ListInsert(La, ++La_len, e); // La中不存在和 e 相同的数据元素,则插入之 }//for } // union
3.线性表的顺序表示
- 线性表的顺序表示又称为顺序存储结构或顺序映像。
- 顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
简言之,逻辑上相邻,物理上也相邻 - 顺序存储方法:用一组地址连续的存储单元依次存储线性表的元素,可通过数组V[n]来实现。
- 顺序表的特点:
- 逻辑上相邻的数据元素,其物理上也相邻;
- 若已知表中首元素在存储器中的位置,则其他元素存放位置亦可求出(利用数组下标)。计算方法是(参见存储结构示意图):
设首元素a1的存放地址为LOC(a1)(称为首地址),
设每个元素占用存储空间(地址长度)为L字节,
则表中任一数据元素的存放地址为:
LOC(ai+1) = LOC(ai)+L
LOC(ai) = LOC(a1) + L *(i-1)
- 顺序表的存储结构示意图:
- 注意:
4.顺序表的基本操作
- 线性表的动态分配顺序存储结构:
# 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 ClearList(Sqlist &L) { if(L.elem==NULL) exit(ERROR); int i; ElemType *p_elem = L.elem; for(i = 0;i < L.length;i++){ *L.elem = NULL; L.elem++; } L.elem = p_elem; L.length = 0; return OK; }
- 线性表的求长度操作:
Status ListLength(SqList L) { return L.length; }
- 线性表元素的修改操作:
status modify(SqList &L, int i, ElemType e){ //注意:i是位置 if (i<1 || i>L.length) //判断位置i是否合法 return ERROR; L.elem[i-1]=e; //或者 *(L.elem+i-1)=e return OK; }
- 线性表元素的插入操作:
Status ListInsert_Sq(SqList &L,int i, ElemType e){ if(i<1 || i>L.length+1) return ERROR if(L.length>=L.listsize){ newbase=(ElemType )realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType)) if(!newbase)exit(OVERFLOW); L.elem=newbase; L.listsize+=LISTINCREMENT } }
- 线性表元素的删除操作:
Status Delete_Sq(SqList &L,int i){ p=&L.elem[i-1] e=*p q=&L.elem[L.length-1] //q=L.elem+L.length-1 for (++p;p<=q;++p) *(p-1)=*p; --L.length; return OK; }
时间效率分析:
5.线性表的链式表示
- 链式存储特点:逻辑上相邻,物理上不一定相邻
- 链表存放示意图:
- 每个存储结点都包含两部分:数据域和指针域(链域) 。
- 在单链表中,除了首元结点外,任一结点的存储位置由其直接前驱结点的链域的值指示。
- 与链式存储有关的术语
- 何谓头指针、头结点和首元结点?
- 如何表示空表?
- 头结点的数据域内装的是什么?
-
循环链表:表中最后一个结点的指针域指向头结点,整个链表形成一个环。
-
双向链表:结点有两个指针域,一个指向直接前驱,一个指向直接后继。
6.链表的基本操作
- 单链表的读取(或修改)
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; }
-
单链表的插入
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; }
- 单链表的删除
Status ListDelete_L(LinkList &L,int i,ElemType &e){
p=L; j=0;
while(p->next && j<i-1){p=p->next;++j}
if(!p->next || j>i-1) return ERROR;
//p指向第i-1个结点
//判断p->next ,是要保证p的后面还有结点。
q=p->next;
e=q->data;
p->next=q->next;
free(q)
return OK;
}
- 单链表的建立和输出
头插法:每次都插入到头结点的后面,即链表的最前面。 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
- 求单链表的长度
void len_LL (LinkList L,int &len){ len=0; LinkList p; for(p=L->next;p!=NULL;p=p->next) len++; }
- 单链表的遍历
void travle(LinkList L){ LinkList p cout<<"建立的链表为:"; for(p=L->next;p!=NULL;p=p->next) cout<<p->data<<" "; }
- 双向链表的插入操作:
Status ListInsert_DuL(DuLinkList &L,int i, ElemType e){ If(!(p=GetElemP_DuL(L,i))) return ERROR; If(!(s=(DuLinkList)malloc(sizeof(DulNode)))) return ERROR; s->data=e; s->prior=p->prior; p->prior->next=s; s->next=p; p->prior=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; }
时间效率分析
空间效率分析
讨论1:线性表的逻辑结构特点是什么?其顺序存储结构和链式存储结构的特点是什么?
讨论2:顺序存储和链式存储各有哪些优缺点?
讨论3:在什么情况下用顺序表比链表好?
-
总结
顺序表
链表
基于空间考虑
分配方式
静态分配。程序执行之前必须明确规定存储规模,若线性表长度n变化较大,则存储规模难于预先确定,估计过大将造成空间浪费,估计过小又将使空间溢出机会增多。
动态分配。只要内存空间尚有空闲就不会产生溢出,因此当线性表的长度变化较大,难以估计其存储规模时,以采用动态链表作为存储结构为好。
存储密度
=1.当线性表的长度变化不大,易于事先估计其大小时,为了节约存储空间,宜采用顺序存储作为存储结构。
<1
基于时间考虑
存取方法
随机存取结构。对表中任一结点都可在O(1)时间内直接取得。若线性表的操作主要是进行查找,很少做插入和删除操作,则采用顺序表作为存储结构为宜。
顺序存取结构。链表中的结点需从头指针起顺着链扫描才能取得。
插入删除操作
在顺序表中进行插入和删除操作,平均要移动表中一半的结点,尤其是每个结点的信息量较大时,移动结点的开销就相当可观。
在链表中的任何位置插入和删除,都只需要修改指针。对于频繁进行插入和删除操作的线性表,宜采用链表作存储结构。若表的插入和删除主要发生表的首尾两端,则采用尾指针表示的单循环链表为宜。
二.练习题
题组一:
题组二:
一、填空
1. 在顺序表中插入或删除一个元素,需要平均移动 n/2或n-1/2 元素,具体移动的元素个数
与 插入或删除元素的位置 有关。
2. 向一个长度为n的向量的第i个元素(1≤i≤n+1)之前插入一个元素时,需向后移动
n-i+1 个元素。
3. 向一个长度为n的向量中删除第i个元素(1≤i≤n)时,需向前移动 n-i 个元素。
4. 在单链表中,除了首元结点外,任一结点的存储位置由 前驱结点的后继指针 指示。
二、简答题
1. 试比较顺序存储结构和链式存储结构的优缺点。在什么情况下用顺序表比链表好?
顺序表 优点:存储密度大 存储空间利用率高
缺点:插入或删除元素时不方便
链式存储 优点:插入或删除元素时很方便
缺点:存储密度小 存储空间利用率低
在插入和删除情况比较少的情况下用顺序表比链表好
2 . 描述以下三个概念的区别:头指针、头结点、首元结点(第一个元素结点)。在单链表中设置头结点的作用是什么?
头指针: 指向链表第一个结点的指针
头节点:在首元结点之前附设的一个结点,该结点不存储数据元素,其指针指向首元节点,其作用主要是为了方便对链表的操作
首元结点:链表中存储第一个数据元素的结点
三、线性表具有两种存储方式,即顺序方式和链接方式。现有一个具有五个元素的线性表L={23,17,47,05,31},若它以链接方式存储在下列100~119号地址空间中,每个结点由数据(占2个字节)和指针(占2个字节)组成,如下所示:
05
U
17
X
23
V
31
Y
47
Z
^
^
100
120
其中指针X,Y,Z的值分别为多少?该线性表的首结点起始地址为多少?末结点的起始地址为多少?
X:116 Y:NULL Z:100
首结点起始地址108 末结点起始地址为112
四、编程题
1. 写出在顺序存储结构下将线性表逆转的算法,要求使用最少的附加空间。
void Reverse(SqList& L) { for (int i = 0; i <= (L.length - 1) / 2; i++) { int temp = L.data[i]; L.data[i] = L.data[length - 1-i]; L.data[length-1-i] = temp; } }
- 编写程序,将若干整数从键盘输入,以单链表形式存储起来,然后计算单链表中结点的个数(其中指针P指向该链表的第一个结点)。
#include <iostream> using namespace std; #define ElemType int #define Status int #define MAXSIZE 100 typedef struct LNode { ElemType data;//数据域 struct LNode* next;//指针域 }LNode, * LinkList; Status InitList(LinkList& L) { L = new LNode; L->next = NULL; return 1; } Status InsertList(LinkList& L, int pos, ElemType e) { LNode* s; LinkList p = L; int j = 0; while (p&&(j<pos-1)) { p = p->next; ++j; } if (!p || j > pos - 1) { return 0; } s = new LNode; s->data = e; s->next = p->next; p->next = s; return 1; } int main() { LinkList P; InitList(P); int num; cout << "请输入整数,按ctrl+z结束输入" << endl; int Length = 1; while (cin>>num) { Length++; InsertList(P, Length, num); } cout <<"单链表结点个数为:"<< Length-1 << endl; }
题组三: