线性表
线性表的定义
线性表:零个或多个数据元素的有限序列
首先它是一个序列。元素之间是由顺序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其他每个元素都有且只有一个前驱和后继。
其次线性表强调是有限的。即元素个数是有限的。
再较复杂的线性表中,一个数据元素可以由若干个数据项组成。
线性表的顺序存储结构
顺序存储定义
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
可以用一维数组来实现顺序存储结构,即把第一个数据元素存到数组下标为0的位置中,接着把线性表相邻的元素存储在数组中相邻的位置。
顺序存储的结构代码
typedef int ElemType; //ElemType类型根据实际情况而定,这里假设为int struct SqList { ElemType data[MAXSIZE]; //数组,存储数据元素 int length; //线性表当前长度 };
顺序存储结构所需要的三个属性:
存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置。
线性表的最大存储容量:数组长度MAXSIZE
线性表的当前长度:length。
顺序存储的代码实现
#include <iostream> using namespace std; #define OK 1 #define ERROR 0 #define TURE 1 #define FALSE 0 #define MAXSIZE 20 //储空间初始分配量 typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等 typedef int ElemType; //ElemType类型根据实际情况而定,这里假设为int struct SqList { ElemType data[MAXSIZE]; //数组,存储数据元素 int length; //线性表当前长度 }; //初始化顺序线性表 Status InitList(SqList* L) { L->length = 0; return OK; } //初始条件:顺序线性表L已存在 //操作结果:若L为空表,则返回TRUE,否则返回FALSE Status ListEmpty(SqList L) { if (L.length == 0) { return TURE; } else { return FALSE; } } //初始条件:顺序线性表L已存在 //操作结果:将L重置为空表 Status ClearList(SqList* L) { L->length = 0; return OK; } //初始条件:顺序线性表L已存在 //操作结果:返回L中数据元素个数 int ListLength(SqList L) { return L.length; } //初始条件:顺序线性表L已存在,1≤i≤ListLength(L) //操作结果:用e返回L中第i个数据元素的值,注意i是指位置,第1个位置的数组是从0开始 Status GetElem(SqList L, int i, ElemType* e) { if (L.length == 0 || i<1 || i>L.length) { return ERROR; } *e = L.data[i - 1]; return OK; } // 初始条件:顺序线性表L已存在 // 操作结果:返回L中第1个与e满足关系的数据元素的位序。 // 若这样的数据元素不存在,则返回值为0 int LocateElem(SqList L, ElemType e) { if (L.length == 0) { return 0; } for (int i = 0; i < L.length; i++) { if (L.data[i] == e) { return i + 1; } } return 0; } // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) // 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 Status ListInsert(SqList* L, int i, ElemType e) { if (L->length == MAXSIZE) //顺序线性表已经满 { return ERROR; } if (i<1 || i>L->length+1) //当i比第一位置小或者比最后一位置后一位置还要大时 { return ERROR; } if (i <= L->length) //若插入数据位置不在表尾 { for (int k = L->length - 1; k >= i - 1; k--) //要插入位置之后的数据元素向后移动一位 { L->data[k + 1] = L->data[k]; } } L->data[i - 1] = e; //将新元素插入 L->length++; return OK; } // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) // 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 Status ListDelete(SqList* L, int i, ElemType* e) { if (L->length == 0) //线性表为空 { return ERROR; } if (i<1 || i>L->length) //删除位置不正确 { return ERROR; } *e = L->data[i - 1]; if (i <= L->length) //如果删除不是最后位置 { for (int k = i; k < L->length; k++) { L->data[k-1] = L->data[k]; } } L->length--; return OK; } // 初始条件:顺序线性表L已存在 // 操作结果:依次对L的每个数据元素输出 Status ListTraverse(SqList L) { for (int i = 0; i < L.length; i++) { cout<<L.data[i]<<" "; } cout << endl; return OK; } //将所有的在线性表Lb中但不在La中的数据元素插入到La中 void unionL(SqList* La, SqList Lb) { ElemType e; //声明与La和Lb相同的数据元素e int La_len = ListLength(*La); //获取线性表La的长度 for (int i = 1; i <= Lb.length; i++) { GetElem(Lb, i, &e); //取Lb中第i个数据元素赋给e if (!LocateElem(*La, e)) //La中不存在和e相同数据元素 { ListInsert(La, La_len++, e); //插入 } } } int main() { SqList L; SqList Lb; ElemType e; Status i; int j, k; i = InitList(&L); cout << "初始化L后:L.length = " << L.length << endl; for (j = 1; j <= 5; j++) i = ListInsert(&L, 1, j); cout << "在L的表头依次插入1~5后:L.data = "; ListTraverse(L); cout << "L.length = " << L.length << endl; i = ListEmpty(L); cout << "L是否空:i = " << i << " (1:是 0:否)" << endl; i = ClearList(&L); cout << "清空L后:L.length = " << L.length << endl; i = ListEmpty(L); cout<<"L是否空:i = "<< i<< " (1:是 0:否)" << endl; for (j = 1; j <= 10; j++) ListInsert(&L, j, j); cout << "在L的表尾依次插入1~10后:L.data = "; ListTraverse(L); cout << "L.length = " << L.length; ListInsert(&L, 1, 0); cout<<"在L的表头插入0后:L.data="; ListTraverse(L); cout << "L.length = " << L.length << endl; GetElem(L, 5, &e); cout << "第5个元素的值为: " << e << endl; for (j = 3; j <= 4; j++) { k = LocateElem(L, j); if (k) { cout << "第" << k << "个元素的值为" << j << endl; } else { cout << "没有值为" << j << "的元素" << endl; } } k = ListLength(L); // k为表长 for (j = k + 1; j >= k; j--) { i = ListDelete(&L, j, &e); // 删除第j个数据 if (i == ERROR){ cout << "删除第" << j << "个数据失败" << endl; } else{ cout << "删除第" << j << "个的元素值为:" << e << endl; } } cout << "依次输出L的元素:"; ListTraverse(L); j = 5; ListDelete(&L, j, &e); // 删除第5个数据 cout << "删除第" << j << "个的元素值为: " << e << endl; cout << "依次输出L的元素:"; ListTraverse(L); //构造一个有10个数的Lb i = InitList(&Lb); for (j = 6; j <= 15; j++) i = ListInsert(&Lb, 1, j); unionL(&L, Lb); cout << "依次输出合并了Lb的L的元素:"; ListTraverse(L); system("pause"); return 0; }
顺序存储的优缺点
优点:
1. 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
2. 可以快速地存取表中任一位置的元素
缺点:
1. 插入和删除操作需要移动大量元素
2. 当线性表长度变化较大时,难以确定存储空间的容量
3. 造成存储空间的“碎片”
线性表的链式存储结构
链式存储的定义
为了表示每个数据元素ai与其直接后继数据元素ai+1的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据元素ai的存储映像,称为结点。n个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,...an)的链式存储结构。
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着这些数据元素可以存在内存未被占用的任意位置。
把链表中第一个结点的存储位置叫做头指针,整个链表的存取必须是从头指针开始进行。之后的每个结点,其实就是上一个的后继指针指向的位置。最后一个结点指针为空。
有时为了更方便地对链表进行操作,会在单链表地第一个结点前附设一个结点,称为头结点。头结点地数据域可以不存储任何信息,也可以存储如线性表地长度等附加信息,头结点地指针存储指向第一个结点的指针。
头指针和头结点的异同
头指针:
1. 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
2. 头指针具有标识作用,所以常用头指针冠以链表的名字
3. 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
头结点:
1. 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可存放链表的长度)
2. 有了头结点,对于第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了
3. 头结点不一定是链表必须要素
单链表
单链表定义
当线性表链式存储结构中链表的每个结点中只包含一个指针域,就叫做单链表。
单链表的结构代码
typedef int ElemType; struct List { int data; List* next; };
假设 p 是指向线性表第 i 个元素的指针,则该结点 ai 的数据域可以用 p->data 来表示,p->data 的值是一个数据元素,结点 ai 的指针域可以用 p->next 来表示,p->next 的值是一个指针,p->next 指向第 i+1 个元素,即指向 ai+1 的指针。
单链表的代码实现
#include <iostream> #include <time.h> using namespace std; #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 20 typedef int Status; typedef int ElemType; struct List { int data; List* next; }; // 初始化链式线性表 Status InitList(List* L) { //L = new List(); // 产生头结点,并使L指向此头结点 L->next = NULL; //指针域为空 return OK; } // 初始条件:链式线性表L已存在。 // 操作结果:若L为空表,则返回TRUE,否则返回FALSE Status ListEmpty(List L) { if (L.next){ return FALSE; } else{ return TRUE; } } //初始条件:链式线性表L已存在。 //操作结果:将L重置为空表 Status ClearList(List* L) { List* p; List* q; p = L->next; //p指向第一个结点 while (p) { q = p->next; delete p; p = q; } L->next = NULL; //头结点指针域为空 return OK; } //初始条件:链式线性表L已存在 //操作结果:返回L中数据元素个数 int ListLength(List L) { int i = 0; List* p = L.next; // p指向第一个结点 while (p) { i++; p = p->next; } return i; } //初始条件:链式线性表L已存在,1≤i≤ListLength(L) //操作结果:用e返回L中第i个数据元素的值 Status GetElem(List L, int i, ElemType* e) { int j = 0; //计数器 List* p = L.next; // 让p指向链表的第一个结点 while (p && j < i) // p不为空或者计数器j还没有等于i时,循环继续 { p = p->next; //让p指向下一个结点 ++j; } if (!j || j > i) { return ERROR; //第i个元素不存在 } *e = p->data; //取第i个元素的数据 return OK; } // 初始条件:链式线性表L已存在 // 操作结果:返回L中第1个与e满足关系的数据元素的位序。 // 若这样的数据元素不存在,则返回值为0 int LocateElem(List L, ElemType e) { int i = 0; List* p = L.next; while (p) { i++; if (p->data == e) // 找到这样的数据元素 { return i; } p = p->next; } return 0; } // 初始条件:链式线性表L已存在,1≤i≤ListLength(L), // 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 Status ListInsert(List* L, int i, ElemType e) { int j = 1; List* p = L; List* s; while (p && j < i) // 寻找第i个结点 { p = p->next; ++j; } if (!p || j > i) { return ERROR; // 第i个元素不存在 } s = new List(); //生成新结点 s->data = e; s->next = p->next; //将p的后继结点赋值给s的后继 p->next = s; //将s赋值给p的后继 return OK; } // 初始条件:链式线性表L已存在,1≤i≤ListLength(L) // 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 Status ListDelete(List* L, int i, ElemType* e) { int j = 1; List* p = L; List* q; while (p->next && j < i) //遍历寻找第i个元素 { p = p->next; ++j; } if (!(p->next) || j > i) { return ERROR; //第i个元素不存在 } q = p->next; p->next = q->next; //将q的后继赋值给p的后继 *e = q->data; //将q结点中的数据给e delete q; //让系统回收此结点,释放内存 return OK; } // 初始条件:链式线性表L已存在 // 操作结果:依次对L的每个数据元素输出 Status ListTraverse(List L) { List* p = L.next; while (p) { cout<<p->data<<" "; p = p->next; } cout << endl; return OK; } // 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) void CreateListHead(List* L, int n) { List* p; srand((unsigned int)time(NULL)); //初始化随机数种子 //L = new List(); L->next = NULL; //先建立一个带头结点的单链表 for (int i = 0; i < n; i++) { p = new List(); //生成新结点 p->data = rand() % 100 + 1; //随机生成100以内的数字 p->next = L->next; L->next = p; //插入到表头 } } // 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) void CreateListTail(List* L, int n) { List* p; List* q = L; // q为指向尾部的结点 srand((unsigned int)time(NULL)); //L = new List(); L->next = NULL; for (int i = 0; i < n; i++) { p = new List(); // 生成新结点 p->data = rand() % 100 + 1; q->next = p; // 将表尾终端结点的指针指向新结点 q = p; // 当前的新结点定义为表尾终端结点 } q->next = NULL; // 表示当前链表结束 } int main() { int k, j; List* L = new List(); ElemType e; Status i; i = InitList(L); cout << "初始化L后:ListLength(L) = " << ListLength(*L) << endl; for (j = 1; j <= 5; j++){ i = ListInsert(L, 1, j); } cout << "在L的表头依次插入1~5后:L.data = "; ListTraverse(*L); cout<<"ListLength(L) = "<< ListLength(*L) << endl; i = ListEmpty(*L); cout << "L是否空:i = (1:是 0:否) " << i << endl; i = ClearList(L); cout<<"清空L后:ListLength(L) = "<< ListLength(*L) << endl; i = ListEmpty(*L); cout << "L是否空:i = (1:是 0:否) " << i << endl; for (j = 1; j <= 10; j++) { i = ListInsert(L, j, j); } cout << "在L的表尾依次插入1~10后:L.data = "; ListTraverse(*L); cout << "ListLength(L) = " << ListLength(*L) << endl; ListInsert(L, 1, 0); cout << "在L的表头插入0后:L.data = "; ListTraverse(*L); cout << "ListLength(L) = " << ListLength(*L) << endl; GetElem(*L, 5, &e); cout << "第5个元素的值为:" << e << endl; for (j = 3; j <= 4; j++) { k = LocateElem(*L, j); if (k) cout << "第" << k << "个元素的值为" << j << endl; else cout << "没有值为" << j << "的元素" << endl; } k = ListLength(*L); //k为表长 for (j = k + 1; j >= k; j--) { i = ListDelete(L, j, &e); //删除第j个数据 if (i == ERROR) cout << "删除第" << j << "个数据失败" << endl; else cout << "删除第" << j << "个的元素的值为:" << e << endl; } cout << "依次输出l的元素:"; ListTraverse(*L); j = 5; ListDelete(L, j, &e); //删除第5个数据 cout << "删除第" << j << "个的元素值为:" << e << endl; cout << "依次输出L的元素:"; ListTraverse(*L); cout << endl; i = ClearList(L); cout<<"清空L后:ListLength(L) = "<< ListLength(*L) << endl; CreateListHead(L, 20); cout << "整体创建L的元素(头插法):"; ListTraverse(*L); cout << endl; i = ClearList(L); cout<<"删除L后:ListLength(L) = " << ListLength(*L) << endl; CreateListTail(L, 20); cout << "整体创建L的元素(尾插法):"; ListTraverse(*L); cout << endl; system("pause"); return 0; }
单链表结构优缺点
单链表结构和顺序存储结构的对比
1. 存储分配方式
1)顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
2)单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
2. 时间性能
1)查找
顺序存储结构 O(1)
单链表 O(n)
2)插入与删除
顺序存储结构需要平均移动表长一半的元素,时间为 O(n)
单链表在线出某位置的指针后,插入和删除时仅为 O(1)
3.空间性能
1)顺序存储结构需要预分配存储空间,分大了浪费,分小了易发生上溢
2)单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
结论:若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构;
当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样可以不需要考虑存储空间的大小问题
静态链表
静态链表的定义
用数组描述的链表叫做静态链表,这种描述方法还有起名叫做游标实现法。
首先让数组的元素都是由两个数据域组成,data 和 cur。也就是说,数组的每个下标都对应一个 data 个一个 cur。数据域data,用来存放数据元素,而游标cur相当于单链表中的next指针,存放该元素的后继在数组中的下标。
静态链表的结构代码
typedef char ElemType; typedef struct { ElemType data; int cur; // 游标(Cursor) ,为0时表示无指向 } StaticLinkList[MAXSIZE];
对数组第一个和最后一个元素作为特殊元素处理,不存数据。通常把未被使用的数组元素称为备用链表。
初始化的数组状态:
静态链表的代码实现
#include <iostream> using namespace std; #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 1000 typedef int Status; typedef char ElemType; // 线性表的静态链表存储结构 typedef struct { ElemType data; int cur; // 游标(Cursor) ,为0时表示无指向 } StaticLinkList[MAXSIZE]; // 将一维数组space中各分量链成一个备用链表,space[0].cur为头指针,"0"表示空指针 Status InitList(StaticLinkList space) { int i; for (i = 0; i < MAXSIZE - 1; i++) space[i].cur = i + 1; space[MAXSIZE - 1].cur = 0; // 目前静态链表为空,最后一个元素的cur为0 return OK; } // 若备用空间链表非空,则返回分配的结点下标,否则返回0 int Malloc_SSL(StaticLinkList space) { int i = space[0].cur; // 当前数组第一个元素的cur存的值 // 就是要返回的第一个备用空闲的下标 if (space[0].cur) { space[0].cur = space[i].cur; // 由于要拿出一个分量来使用了, } // 所以我们就得把它的下一个分量用来做备用 return i; } // 将下标为k的空闲结点回收到备用链表 void Free_SSL(StaticLinkList space, int k) { space[k].cur = space[0].cur; // 把第一个元素的cur值赋给要删除的分量cur space[0].cur = k; // 把要删除的分量下标赋值给第一个元素的cur } // 初始条件:静态链表L已存在 // 操作结果:返回L中数据元素个数 int ListLength(StaticLinkList L) { int j = 0; int i = L[MAXSIZE - 1].cur; while (i) { i = L[i].cur; j++; } return j; } // 在L中第i个元素之前插入新的数据元素e Status ListInsert(StaticLinkList L, int i, ElemType e) { int j, k, l; k = MAXSIZE - 1; // 注意k首先是最后一个元素的下标 if (i < 1 || i > ListLength(L) + 1) return ERROR; j = Malloc_SSL(L); // 获得空闲分量的下标 if (j) { L[j].data = e; // 将数据赋值给此分量的data for (l = 1; l <= i - 1; l++) // 找到第i个元素之前的位置 k = L[k].cur; L[j].cur = L[k].cur; // 把第i个元素之前的cur赋值给新元素的cur L[k].cur = j; // 把新元素的下标赋值给第i个元素之前元素的cur return OK; } return ERROR; } // 删除在L中第i个数据元素 Status ListDelete(StaticLinkList L, int i) { int j, k; if (i < 1 || i > ListLength(L)) return ERROR; k = MAXSIZE - 1; for (j = 1; j <= i - 1; j++) k = L[k].cur; j = L[k].cur; L[k].cur = L[j].cur; Free_SSL(L, j); return OK; } Status ListTraverse(StaticLinkList L) { int j = 0; int i = L[MAXSIZE - 1].cur; while (i) { cout<<L[i].data<<" "; i = L[i].cur; j++; } cout << endl; return OK; } int main() { StaticLinkList L; Status i; i = InitList(L); cout << "初始化L后:L.length = " << ListLength(L) << endl; i = ListInsert(L, 1, 'F'); i = ListInsert(L, 1, 'E'); i = ListInsert(L, 1, 'D'); i = ListInsert(L, 1, 'B'); i = ListInsert(L, 1, 'A'); cout << "在L的表头依次插入FEDBA后:L.data = "; ListTraverse(L); i = ListInsert(L, 3, 'C'); cout << "在L的“B”与“D”之间插入“C”后:L.data = "; ListTraverse(L); i = ListDelete(L, 1); cout << "在L的删除“A”后:L.data = " << endl; ListTraverse(L); system("pause"); return 0; }
静态链表的优缺点
优点:
1. 在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点
缺点:
1. 没有解决连续存储带来的表厂难以确定的问题
2. 失去了顺序存储结构随机存取的特性
静态链表其实是为了给没有指针的高级语言设计的一种实现单链表能力的方法。
循环链表
循环链表的定义
将单链表中终点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。
也可以用指向终端结点的尾指针来表示循环链表,此时查找开始结点和终端结点就很方便了。
双向链表
双向链表的定义
双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
双向链表的结构代码
//线性表的双向链表存储结构 typedef struct DulNode { ElemType data; struct DuLNode *prior; //直接前驱指针 struct DuLNode *next; //直接后继指针 } DulNode, *DuLinkList;
如图所示:
在插入和删除时,需要更改两个指针变量,应注意顺序
插入操作
删除操作
总结
待更新~