第二章:线性表
定义、特点
一、定义
诸如此类由n(n>=0)个数据特性相同的元素构成的有限序列称为线性表,线性表中元素的个数n(n>=0)定义为线性表的长度以,n=0时称为空表。
二、特点
(1)存在唯一一个被称为“第一个”的数据元素;
(2)存在唯一一个被称为“最后一个”的数据元素;
(3)除第一个之外,结构中的每个数据元素均只有一个前驱;
(4)除最后一个之外,结构中的每个数据元素均只有一个后继。
顺序表
一、定义
线性表的顺序存储又称为顺序表。它是用一组地址连续的存储单元,依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。
二、存储结构描述
#define MAXSIZE 100 //顺序表可能达到的最大长度
typedef struct
{
ElemType *elem; //存储空间的基地址
int length; //当前长度
}SqList; //顺序表的结构类型为SqList
(打包,课本例子:多项式
#define MAXSIZE 100 //多项式可能达到的最大长度
typedef struct //多项式非零项的定义
{
float coef; //系数
int expn; //指数
}Polynomial;
typedef struct
{
Polynomial *elem; //存储空间的基地址
int length; //多项式中当前项的个数
}SqList; //多项式的顺序存储结构类型为SqList
三、基本操作的实现
-
初始化
Status InitList(SqList &L) { L.elem = new ElemType[MAXSIZE]; //分配空间 if (!L.elem) exit(OVERFLOW); //分配失败退出 L.length = 0; //空表长度为0 return OK; }
-
取值(时间复杂度:O(1))
Status GetElem(SqList &L, ElemType &e) { if (i<1||i>L.length) return ERROR; //判断i值是否合理,不合理返回ERROR e = L.elem[i-1]; //elem[i-1]单元存储第i个数据元素 return OK; }
-
查找(时间复杂度:O(n))
int LocateElem(SqList L, ElemType e) { for (int i=0;i<L.length;i++) { if (L.elem[i]==e) return i+1; //查找成功,返回序号i+1 } return 0; //查找失败,返回0 }
-
插入(时间复杂度:O(n))
Status ListInsert(SqList &L, int i, ElemType e) { if ((i<1)||(i>L.length+1)) return ERROR; //i值不合法 if (L.length == MAXSIZE) return ERROR; //当前存储空间已满 for (int j=L.length-1;j>=i-1;j--) { L.elem[j+1] = L.elem[j]; //插入位置及之后的元素后移 } L.elem[i-1] = e; //将新元素e放入第i个位置 ++L.length; //表长加1 return OK: }
-
删除(时间复杂度:O(n))
Status ListDelete(SqList &L, int i) { if ((i<1)||(i>L.length+1)) return ERROR; //i值不合法 for (int j=i;j<=L.length-1;j++) { L.elem[j-1] = L.elem[j]; //被删除元素之后的元素前移 } --L.length; //表长减1 return OK; }
四、算法分析
时间复杂度:O(n)
缺点:在做插入或删除
链表
一、存储结构描述
typedef struct LNode
{
ElemType data;//数据域
struct LNode *next;//指针域
}LNode,*LinkList; //LinkList为指向结构体LNode的指针类型
二、基本操作的实现
-
初始化
Status InitList(LinkList &L) { L = new LNode;//生成头结点 这样删除等操作就不必分第一个结点和其他了 L->next = NULL; return 1; }
-
取值(时间复杂度:O(n))
Status *GetElem(LinkList &L,int i, ElemType &e) { int j = 1; LNode *p = L->next; while(p&&j<i) { p=p->next; j++; } if (!p||j>i) return ERROR; e = p->data; return 1; }
-
查找(时间复杂度:O(n))
LNode *LocateElem(LinkList &L ,ElemType e) { LNode *p = L->next; while(p!=NULL&&p->data!=e) p = p->next; return p; }
-
插入(时间复杂度:O(n))
Status ListInsert(LinkList &L,int i,ElemType e) { LNode* s;LinkList p=L;int j=0; while(p&&(j<i-1))//j指到i-1位置或者p已经到最后时跳出 { p=p->next; ++j; } if(!p||j>i-1) return 0; s=new LNode; s->data=e; s->next=p->next; p->next=s; return 1; }
-
删除(时间复杂度:O(n))
Status ListDelete(LinkList &L,int i) { LNode* s;LinkList p=L;int j=0; LinkList q; while(p&&(j<i-1))//j指到i-1位置 { p=p->next; ++j; } if(!(p->next)||j>i-1) return 0; q=p->next; p->next=q->next; free(q);//释放空间 return true; }
-
创建单链表(时间复杂度:O(n))
//头插法 void CreateList(LinkList *&L, int n) { LNode *L = new LNode; //创建一个带头结点的空链表 L->next = NULL; for (int i=0;i<n;i++) { LNode *p = new LNode; cin >> p->data; p->next=L->next; //将新结点*p插入到头结点之后 L->next=p; }
//尾插法 void CreatList_R(LinkList &L, int n) { LinkList r;//定义一个尾指针 L=new LNode; //L为头结点 L->next=NULL; r=L; //尾指针r指向头结点 for(int i=0;i<n;++i) { LinkList p = new LNode; cin>>p->data; p->next=NULL; //将新结点*p插入尾结点*r之后 r->next=p; r=p; //r指向新的尾结点*p } }
线性表应用
一、线性表的合并
void Merge(SqList &LA,SqList &LB)
{
int n = Length(LA),m = Length(LB);
int i,x;
for (i=1;i<=n;i++)
{
GetElem(LB,i,x); //取LB中第i个元素赋值给x
if (!LocateElem(LA, x)) //LA中不存在和x相同的元素
ListInsert(LA,m++,x); //将x插在LA的最后
}
}
二、有序表的合并
//顺序有序表
void MergeList_Sq(SqList LA, SqList LB, SqList &LC)
{
LC.length = LA.length + LB.length;
LC.elem = new ElemType[LC.length];
LNode *pa, *pb, *pc;
LNode *pa_last, *pb_last;
pc = LC.elem;
pa = LA.elem;
pb = LB.elem;
pa_last = LA.elem + LA.length - 1;
pb_last = LB.elem + LB.length - 1;
//逻辑上好理解 LA LB 一起找 小的就塞到LC 如果都其中一个空了 另一个就全塞进去
while (pa <= pa_last && pb <= pb_last) //均为达到表尾
{
if (*pa <= *pb) *pc++ = *pa++; //取两表中较小的值插入到LC的最后
else *pc++ = *pb++;
}
while (pa <= pa_last) *pc++ = *pa++; //LB以达到表尾,依次将LA的剩余元素插入到LC的最后
while (pb <= pb_last) *pc++ = *pb++; //同理
}
//链式有序表
void MergeList_L(LinkList &LA, LinkList &LB, LinkList &LC)
{
LinkList pa,pb,pc;
pa=LA->next;pb=LB->next; //pa和pb的初值分别指向两个表的第一个结点
LC=LA; //用LA的头结点作为LC的头结点
pc=LC; //pc指向LC的头结点
while(pa&&pb)
{//LA和LB均未达到表尾,则依次“摘取”两表中较小的结点插入到C的最后
if(pa->data <= pb->data)
{
pc->next=pa;
pc=pc->next;
pa=pa->next;
}
else
{
pc->next=pb;
pc=pc->next;
pb=pb->next;
}
} //while循环结束,有一表或两表为空
pc->next=pa? pa:pb; //将非空表的剩余段插入到pc所指结点之后
//相当于
/*if(pa)
{
pc-> next = pa;
else
pc-> next = pb;
}*/
delete LB;
}
作业练习题
一、选择填空
- For a sequentially stored linear list of length N, the time complexities for query and insertion are O(1) and O(N), respectively. (T)
(翻译:对于长度为N的顺序存储的线性列表,查询和插入的时间复杂度分别为O(1)和O(N))
查询相当于访问结点,只需要按照下标访问。
- If the most commonly used operations are to visit a random position and to insert and delete the last element in a linear list, then sequential storage works the fastest. (T)
(翻译:如果最常用的操作是访问随机位置并插入和删除线性列表中的最后一个元素,则顺序存储的工作速度最快。)
顺序存储结构可以实现随机访问并插入,删除最后一个元素不需要进行易懂,所以速度最快。
- In a singly linked list of N nodes, the time complexities for query and insertion are O(1) and O(N), respectively. (F)
(翻译:在N个节点的单链接列表中,查询和插入的时间复杂度分别为O(1)和O(N)。)
链表中操作时间复杂度都不为O(1),查询插入操作时间复杂度都为O(n)。
- 将N个数据按照从小到大顺序组织存放在一个单向链表中。如果采用二分查找,那么查找的平均时间复杂度是O(logN)。 (F)
二分查找不可以用于链表
- 下列代码的功能是返回带头结点的单链表L的逆转链表。(代码填空)
List Reverse( List L ) { Position Old_head, New_head, Temp; New_head = NULL; Old_head = L->Next; while ( Old_head ) { Temp = Old_head->Next; *Old_head->Next = New_head;* New_head = Old_head; Old_head = Temp; } *L->Next = New_head;* return L; }
二、代码实战
-
在有序链表中插入数据 (20分)
给定一批严格递增排列的整型数据,给定一个x,若x不存在,则插入x,要求插入后保持有序。存在则无需任何操作。
输入格式:
输入有两行: 第一个数是n值,表示链表中有n个数据。后面有n个数,分别代表n个数据。 第二行是要插入的数。
输出格式:
输出插入后的链表数据,以空格分开。行末不能有多余的空格。
输入样例1:
在这里给出一组输入。例如:5 1 3 6 9 11 4
输出样例1:
在这里给出相应的输出。例如:1 3 4 6 9 11
输入样例2:
在这里给出一组输入。例如:5 1 3 6 9 11 3
输出样例2:
在这里给出相应的输出。例如:1 3 6 9 11
代码实现
#include <iostream> using namespace std; typedef int ElemType; typedef int status; typedef struct LNode { ElemType data; struct LNode *next; }LNode, *LinkList; status Initial (LinkList &L) { L = new LNode; L->next = NULL; return 1; } void CreateList(LinkList &L, ElemType n) { //输入第n个元素,尾插法建立带表头节点的单链表L LNode *p,*r; ElemType i; L = new LNode; L->next = NULL; r = L; for(i=0;i<n;i++) { p = new LNode; //生成新结点 cin >> p->data; //输入元素值 p -> next = NULL; r -> next = p; //将p加到表尾 r = p; //r指向新的尾结点 } } status ListInsert (LinkList &L, ElemType x) { LNode *p = L; if (p->next == NULL) { LNode *q = new LNode; q->data = x; q->next = NULL; p->next = q; p = q; return 0; } while (p->next != NULL) { LNode* temp = p; p = p->next; if (p->data == x) return 0; if (p->data > x) { LNode* q = new LNode; q->data = x; q->next = p; temp->next = q; return 0; } } LNode* q = new LNode; q->data = x; q->next = NULL; p->next = q; p = q; return 0; } void Output (LinkList &L) { LNode *p = L->next; while (p != NULL) { cout << p->data; if (p->next != NULL) cout << " "; p = p->next; } } int main() { ElemType n, x, i; LinkList L; cin >> n; CreateList (L, n); cin >> x; ListInsert (L, x); Output(L); return 0; }
-
实现链表的删除、插入、遍历操作,并退出
代码实现
#include <iostream> using namespace std; typedef int Status; #define ElemType int typedef struct node { ElemType data; struct node *next; }LNode; typedef struct { LNode *head; LNode *tail; }List; void InitList (LNode &L, List &a) //建立链表 { a.head = new LNode; a.head->next = NULL; a.tail = a.head; } void DestroyList(LNode &L, List &a) { LNode *p = a.head , *q; while (p!=NULL) //当p指向结点不为空 { q = p->next; //q指向p的下一结点 delete p; //回收p指向的结点空间 p = q; //p指向q指向的结点 } } void A(LNode &L, List &a, ElemType &x) //增加操作 { LNode *p; p = new LNode; //生成新结点 p -> data = x; p -> next = NULL; a.tail-> next = p; //将p加到表尾 a.tail = p; //r指向新的尾结点 cout << "Insert " << x << " OK" << endl; } /* 该方法删除了头结点,直接将首元结点当成了头结点;不需要考虑删除 // 最后一个结点时会删掉尾指针的问题。 void D(List &L) { if (L.head->next == NULL) { cout << "Empty list" << endl; return; } LNode *p = L.head; L.head = L.head->next; cout << "Delete " << p->data << " OK" << endl; delete p; }*/ void D(LNode &L, List &a) //删除操作 { if (a.head->next==NULL) cout << "Empty list" << endl; //判断链表是否为空 else { LNode *q; q = a.head->next; //指向头结点 ElemType x; x = q->data; if (q==a.tail) //重置尾结点 { a.head->next = NULL; a.tail = a.head; } else a.head->next = q->next; delete q; //释放q空间 cout << "Delete " << x << " OK" << endl; } } void L(LNode &L, List &a) //遍历操作 { LNode *p = a.head->next; //指向头结点 if (a.head->next==NULL) cout << "Empty list" << endl; //判断链表是否为空 else { while (p!=NULL) { cout << p->data ; p = p->next; if (p!=a.tail->next) cout << " "; // 输出数据以空格分隔 } } } int main() { LNode a; List b; ElemType m; InitList(a, b); char t[2]; //存储输入的数据 while(1) { for(int i=0;i<1;i++) //分情况考虑输入字符个数 { cin >> t[i]; } if(t[0]=='A') { for(int i=1;i<2;i++) { cin >> t[i]; } m = t[1]-'0'; //字符型转化为整型 A(a, b, m); } else if (t[0]=='D') D(a,b); else if (t[0]=='L') L(a, b); else if (t[0]=='E') { cout << "88"; break; //退出程序 } } DestroyList(a, b); return 0; }
运行结果
-
两个有序序列的中位数
已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列A0 ,A1 ,⋯,AN−1 的中位数指A(N−1)/2 的值,即⌊(N+1)/2⌋个数(A0 为第1个数)。输入格式:
输入分三行。第一行给出序列的公共长度N(0<N≤100000),随后每行输入一个序列的信息,即N个非降序排列的整数。数字用空格间隔。输出格式:
在一行中输出两个输入序列的并集序列的中位数。输入样例1:
5 1 3 5 7 9 2 3 4 5 6
输出样例1:
4
输入样例2:
6 -100 -10 1 1 1 1 -50 0 2 3 4 5
输出样例2:
1
代码实现
#include <iostream> using namespace std; typedef int Status; typedef int ElemType; #define MAXSIZE 100000 typedef struct { ElemType *elem; int length; } SqList; Status InitList(SqList &L) { L.elem = new ElemType[MAXSIZE]; L.length = 0; return 0; } void Input(SqList L, int N) { for (int i=0;i<N;i++) { cin >> L.elem[i]; } } Status GetElem(SqList L, int n, int &e) { if(n<1||n>L.length) return 0; e = L.elem[n-1]; return 1; } void MixList(SqList L1, SqList L2, SqList &L3, int N) { L3.length = N*2; int i, j, k; i = j = k = 0; for (;i<N,j<N;) { if (L1.elem[i]<=L2.elem[j]) { L3.elem[k] = L1.elem[i]; k++; i++; } else { L3.elem[k] = L2.elem[j]; k++; j++; } } } int main() { int N, e; cin >> N; SqList L1, L2, L3; InitList(L1); InitList(L2); InitList(L3); Input (L1,N);