目录
线性结构的特点
在数据元素的非空有限集中:
-
存在唯一的一个被称作“第一个”的数据元素
-
存在唯一的一个被称作“最后一个”的数据元素
-
除第一个数据元素外,集合中的每个数据元素均只有一个前驱
-
除最后一个数据元素外,集合中的每个数据元素均只有一个后继
线性表的逻辑结构
线性表的定义
一个线性表是n个数据元素的有限序列
-
数据元素可以是一个数、一个符号、也可以是一幅图、一页书或更复杂的信息。
-
同一线性表中的元素必定具有相同特性,即属同一数据对象。
-
数据元素也可由若干个数据项组成。
-
这时常把数据元素称为记录。
-
相邻数据元素之间存在着序偶关系。
线性表的长度
线性表是n个元素的有限序列,它们之间的关系可以排成一个线性序列:
(a1, a2, …, ai, ai+1, …, an)
其中n称作表的长度,当n=0时,称作空表。 ai是第i个数据元素。i是数据元素ai在线性表中的位序,当1<i<n时,ai的直接前驱是ai-1,a1无直接前驱;ai的直接后继是ai+1,an无直接后继。
线性表的特点
-
线性表中所有元素的性质相同。
-
除第一个和最后一个数据元素之外,其它数据元素有且仅有一个前驱和一个后继。第一个数据元素无前驱,最后一个数据元素无后继。
-
数据元素在表中的位置只取决于它自身的序号。
线性表的顺序表示与实现
线性表的顺序存储结构
-
顺序表:用一组地址连续的存储单元存放一个线性表
-
元素地址计算方法:LOC(ai) = LOC(a1) + (i - 1) * L L—一个元素占用的存储单元个数 LOC(ai)—线性表第i个元素的地址
-
特点:
-
实现逻辑上相邻—物理地址相邻
-
实现随机存取
-
-
实现:可用C语言的一维数组实现
顺序表的类型定义
#define LIST_INIT_SiZE 100 // 线性表存储空间的初始分配量
#define LISTINCREMENT 10 // 线性表存储空间的分配增量
typedef struct {
ElemType *elem; // 存储空间地址
int length; // 当前长度
int listsize; // 当前分配的存储容量(以sizeof(ElemType)为单位)
}SqList;
顺序表的基本操作
初始化操作
功能:建立空的顺序表L。
// 初始化操作
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;
} // InitList_Sq
销毁操作
功能:回收为顺序表动态分配的存储空间。
// 销毁操作
Status DestroyList_Sq(SqList &L) {
if (!L.elem) {
return ERROR; // 若表L不存在
}
free(L.elem); // 若表L已存在,回收动态分配的存储空间
L.elem = nullptr;
L.length = 0;
L.listsize = 0;
return OK;
} // DestroyList_Sq
置空操作
功能:若L已存在,重新将其置成空表。
// 置空操作
Status ClearList_Sq(SqList &L) {
if (!L.elem) {
return ERROR; // 若表L不存在
}
L.length = 0; // 若表L已存在,将L置空
return OK;
} // ClearList_Sq
判断空表
功能:判断顺序表是否为空。
// 判断空表
Status ListEmpty(SqList L) {
if (L.length == 0) {
return TRUE;
} else {
return FALSE;
}
} // ListEmpty
求表长
功能:计算顺序表的长度。
// 求表长
int ListLength(SqList L) {
return L.length;
} // ListLength
取元素操作
功能:从顺序表L中取出对应位置的元素。
// 取元素操作
Status GetElem_Sq(SqList L, int i, ElemType &e) {
if ((i < 1) || (i > L.length)) {
return ERROR; // i非法
}
e = L.elem[i - 1]; // 将顺序表中第i个元素赋值给e
return OK;
} // GetElem_Sq
顺序表的插入
功能:在顺序表的对应位置插入一个元素。
// 顺序表的插入
Status ListInsert_Sq(SqList &L, int i, ElemType e) {
// 在顺序线性表L的第i个位置之前插入新的元素e
// i的合法值为1 <= i <= ListLength_Sq(L) + 1
ElemType *q, *p, *newbase;
if (i < 1 || i > L.length + 1) {
return ERROR; // i值不合法
}
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; // 增加存储总量
}
q = &(L.elem[i - 1]); // q为插入位置
for (p = &(L.elem[L.length - 1]); p >= q; --p) {
*(p + 1) = *p; // 插入位置及之后的元素后移
}
*q = e; // 插入e
++L.length; // 表长增1
return OK;
} // ListInsert_Sq
顺序表的删除
功能:删除顺序表中对应位置的元素。
// 顺序表的删除
Status ListDelete_Sq(SqList &L, int i, ElemType &e) {
// 在顺序线性表L中删除第i个元素,并用e返回其值
// i的合法值为1 <= i <= L.length
if ((i < 1) || (i > L.length)) {
return ERROR; // i值不合法或表空
}
ElemType *p, *q;
p = &(L.elem[i - 1]); // p为被删除元素的位置
e = *p; // 被删除元素的值赋给e
q = L.elem + L.length - 1; // 表尾元素的位置
for (++p; p <= q; ++p) {
*(p - 1) = *p; // 被删除元素之后的元素前移
}
--L.length; // 表长减1
return OK;
} // ListDelete_Sq
求数据元素前驱
功能:获取顺序表L对应结点的前驱。
// 求数据元素前驱
Status PriorElem(SqList L, ElemType cur_e, ElemType &pre_e) {
int i = 2;
ElemType *p = L.elem + 1;
while (i <= L.length && *p != cur_e) {
p++;
i++;
}
if (i > L.length) {
return INFEASIBLE;
} else {
pre_e = *--p; // 先减1后赋值
return OK;
}
} // PriorElem
求数据元素后继
功能:获取顺序表L对应结点的后继。
// 求数据元素后继
Status NextElem(SqList L, ElemType cur_e, ElemType &next_e) {
int i = 1;
ElemType *p = L.elem;
while (i < L.length && *p != cur_e) {
i++;
p++;
}
if (i == L.length) {
return INFEASIBLE;
} else {
next_e = *++p;
return OK;
}
} // NextElem
查找
功能:根据所给值查找顺序表中此元素的第一次出现的位置。若存在则返回该位置,若不存在则返回0。
// 查找
int LocateElem(SqList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
ElemType *p;
int i = 1; // i的初始值为第1个元素的位序
p = L.elem; // p的初始值为第1个元素的存储位置
while (i <= L.length && !compare(*p++, e)) {
++i;
}
if (i <= L.length) {
return i;
} else {
return 0;
}
} // LocateElem
遍历
功能:遍历顺序表L中的每一个元素。
// 遍历
Status ListTraverse(SqList L, void(*vi)(ElemType &)) {
ElemType *p;
int i;
p = L.elem;
for (int i = 1; i <= L.length; i++) {
vi(*p++);
}
return OK;
} // ListTraverse
线性表的链式表示与实现
链表是指用一组任意的存储单元来依次存放线性表的结点,这组存储单元即可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。因此,链表中结点的逻辑次序和物理次序不一定相同。
为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息,这个信息称为指针或链。这两部分组成了链表中的结点结构:结点=数据域+指针域。
链式存储结构特点
-
用一组任意的存储单元存储线性表的数据元素
-
利用指针实现了用不相邻的存储单元存放逻辑上相邻的元素
-
每个数据元素ai,除存储本身信息外,还需存储其直接后继的信息
-
结点:
-
数据域:元素本身信息
-
指针域:指示直接后继的存储位置
-
-
与顺序存储相比的一些优势:
-
插入、删除灵活方便,不需要移动结点,只要改变结点中指针域的值即可
-
适合于线性表是动态变化的,不进行频繁查找操作、但经常进行插入删除时使用
-
-
链表的查找只能从头指针开始顺序查找
链表的三种形式
-
线性链表
-
循环链表
-
双向链表
线性链表
结点中只含一个指针域的链表叫线性链表,也叫单链表。
线性链表的类型定义
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode, *LinkList;
LNode *p;
LinkList L;
线性链表的结构
-
*p表示L所指向的结点 (*p).data或p->data表示p指向结点的数据域 (*p).nextp->next表示p指向结点的指针域
-
生成新结点:p=(LinkList)malloc(sizeof(LNode)); 系统回收p结点:free(p)
-
头结点:在单链表第一个结点前附设的一个结点 头结点指针域为空表示线性表为空
链表的基本操作
初始化链表
功能:建空线性链表L。
// 初始化操作
Status InitList_L(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
if (!L) {
exit(OVERFLOW);
}
L->next = nullptr;
return OK;
} // InitList_L
取元素操作
功能:将线性链表中第i个元素赋值给e。
// 初始化操作
Status InitList_L(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
if (!L) {
exit(OVERFLOW);
}
L->next = nullptr;
return OK;
} // InitList_L
按值查找
功能:在链表中,查找是否有结点值等于给定值key的结点,若有的话,则返回首次找到的其值为key的结点的存储位置;否则返回NULL。
// 按值查找
Status LocateNode_L(LinkList L, ElemType key, LNode &e) {
LNode *p;
p = L->next;
while (p && p->data != key) {
p = p->next;
}
if (!p) {
return ERROR;
}
e = *p;
return OK;
} // LocateNode_L
插入操作
功能:在线性链表L的第i个元素结点之前插入一个新元素结点。
// 插入操作
Status ListInsert_L(LinkList &L, int i, ElemType e) {
// 在带头结点的线性链表L中第i元素结点之前插入元素e
LNode *p, *s;
p = L;
int j = 0;
while (p && j < i - 1) {
p = p->next;
++j;
} // 寻找第i-1个元素结点
if (!p || j > i - 1) {
return ERROR;
// i小于1,则j>i-1
// i大于表长+1,则p为空
}
s = (LinkList)malloc(sizeof(LNode)); // 分配新结点
s->data = e;
s->next = p->next;
p->next = s; // 插入新节点
return OK;
} // ListInsert_L
删除操作
功能:在线性链表L中删除第i个元素,并且用e返回其值。
// 删除操作
Status ListDelete_L(LinkList &L, int i, ElemType &e) {
// 在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
LNode *p, *q;
p = L;
int j = 0;
while (p->next && j < i - 1) {
// 寻找第i-1个结点
p = p->next;
++j;
}
if ((!p->next) || j > i - 1) {
return ERROR;
}
q = p->next;
p->next = q->next; // 删除结点
e = q->data; // 用e返回该结点的值
free(q); // 回收(释放)结点空间
return OK;
} // ListDelete_L
头插法建立单链表
功能:建立一个带值单链表L。
// 头插法建立单链表
void CreateList_L(LinkList &L, int n) {
// 逆序输入n个元素的值,建立带表头结点的线性链表
LNode *p;
L = (LinkList)malloc(sizeof(LNode));
L->next = nullptr; // 先建立一个带头节点的单链表
for (int i = n; i > 0; --i) {
p = (LinkList)malloc(sizeof(LNode)); // 生成新结点
cin >> p->data; // 输入元素值
p->next = L->next;
L->next = p; // 插入到表头
}
} // CreateList_L
尾插法建立单链表
功能:建立一个带值单链表L。
// 尾插法建立单链表
void CreateList_L_Tail(LinkList &L, int n) {
// 输入n个元素的值,建立带表头结点的线性链表
L = (LinkList)malloc(sizeof(LNode));
L->next = nullptr; // 先建立一个带头结点的单链表
LNode *r = L;
for (int i = 1; i <= n; i++) {
LNode *p = (LinkList)malloc(sizeof(LNode)); // 生成新结点
cin >> p->data; // 输入元素值
p->next = nullptr;
r->next = p;
r = p;
}
} // CreateList_L_Tail
练习题
递增有序顺序表的插入
输入格式:
第1行输入顺序表长度,第2行输入递增有序的顺序表,第3行输入要插入的数据元素X。
输出格式:
对每一组输入,在一行中输出插入X后的递增的顺序表。
输入样例:
在这里给出一组输入。例如:
5 1 3 5 7 9 6输出样例:
在这里给出相应的输出。例如:
1,3,5,6,7,9,
代码
#include <iostream>
using namespace std;
#define LIST_INIT_SiZE 100 // 线性表存储空间的初始分配量
#define LISTINCREMENT 10 // 线性表存储空间的分配增量
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
typedef struct {
int *elem; // 存储空间地址
int length; // 当前长度
int listsize; // 当前分配的存储容量(以sizeof(ElemType)为单位)
}SqList;
Status _initSqList(SqList &L) {
// 构造一个空的顺序表L
L.elem = (int*)malloc(LIST_INIT_SiZE * sizeof(int));
if (!L.elem) {
exit(OVERFLOW); // 存储分配失败
}
L.length = 0; // 空表长度为0
L.listsize = LIST_INIT_SiZE; // 初始存储容量
return OK;
} // initSqList
Status _addElem(SqList &L, int n) {
int *p;
if (L.length + n > L.listsize) {
int *newbase;
newbase = (int*)realloc(L.elem, (n / LISTINCREMENT + 1) * LIST_INIT_SiZE * sizeof(int)); // 继续分配内存
if (!newbase) {
exit(OVERFLOW); // 存储分配失败
}
L.elem = newbase;
}
L.listsize += (n / LISTINCREMENT + 1) * LISTINCREMENT; // 增加存储容量
for (int i = 0; i < n; ++i) {
cin >> L.elem[i];
L.length++;
}
return OK;
}
Status _bubbleSort(SqList &L) {
for (int slow = 0; slow < L.length; ++slow) {
for (int fast = slow; fast < L.length; ++fast) {
if (L.elem[slow] > L.elem[fast]) {
int temp = L.elem[slow];
L.elem[slow] = L.elem[fast];
L.elem[fast] = temp;
}
}
}
return OK;
}
Status _printList(SqList L) {
for (int i = 0; i < L.length; i++) {
cout << L.elem[i] << ",";
}
}
int main() {
int n;
cin >> n;
SqList L;
_initSqList(L);
_addElem(L, n + 1);
_bubbleSort(L);
_printList(L);
return 0;
}
带密码的约瑟夫问题
带密码的约瑟夫问题:编号为1,2,......,n的n个人按照顺时针方向围坐一圈,每个人有自己的编号(正整数)、姓名和密码(正整数)三个数据项。一开始任选一个正整数作为报数上限值,从第一个人开始顺时针方向自1开始报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向的下一个人开始重新报数,如此下去,直到所有人全部出队为止。设计一个程序来求出出队顺序。
输入格式:
输入人数 n(1≤n≤50),再逐行输入每个人的信息(各项之间用逗号隔开),然后输入报数上限值m。
输出格式:
按出队顺序逐行输出每个人的信息,每人信息一行,数据项之间用逗号隔开
输入样例:
在这里给出一组输入。例如:
5 1,刘三,3 2,李丽,5 3,吴勇,8 4,钱多,2 5,齐民,4 2
输出样例:
在这里给出相应的输出。例如:
2,李丽,5 3,吴勇,8 5,齐民,4 4,钱多,2 1,刘三,3
代码
#include <iostream>
using namespace std;
#define LIST_INIT_SiZE 100
#define LISTINCREMENT 10
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
typedef struct node {
int num;
char name[20];
int passwd;
struct node *next;
} LNode;
typedef struct node *LinkList;
Status InitList(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
if (!L) {
return OVERFLOW;
}
L->next = L;
return OK;
}
Status InsertList(LinkList &L, int n) {
LNode *p;
p = L;
for (int i = 1; i <= n; i++) {
LNode *s;
s = (LinkList)malloc(sizeof(LNode));
scanf("%d,%[^,],%d", &s->num, s->name, &s->passwd);
// printf("%d,%s,%d\n", s->num, s->name, s->passwd); //*
p->next = s;
s->next = L->next;
p = p->next;
}
return OK;
}
Status Out(LinkList &L, int &m, int &n) {
LNode *p = L;
LNode *q;
while (n > 0) {
for (int i = 1; i <= m; i++) {
q = p;
p = p->next;
}
m = p->passwd - 1;
printf("%d,%s,%d\n", p->num, p->name, p->passwd);
q->next = q->next->next;
p = p->next;
n--;
}
return OK;
}
Status Out1(LinkList &L, int &m, int &n) {
LNode *p = L;
for (int i = 0; i < 5; i++) {
p = p->next;
printf("%d,%s,%d\n", p->num, p->name, p->passwd);
// p = p->next;
}
// printf("%d,%s,%d\n", p->num, p->name, p->passwd);
return OK;
}
int main() {
int n, m;
cin >> n;
LinkList L;
InitList(L);
InsertList(L, n);
cin >> m;
Out(L, m, n);
// Out1(L, m, n);
return 0;
}