【数据结构】线性表及其顺序表示和链式表示

目录

线性结构的特点

线性表的逻辑结构

线性表的定义

线性表的长度

线性表的特点

线性表的顺序表示与实现

线性表的顺序存储结构

顺序表的类型定义

顺序表的基本操作

线性表的链式表示与实现

链式存储结构特点

链表的三种形式

线性链表

链表的基本操作

练习题

递增有序顺序表的插入

带密码的约瑟夫问题


线性结构的特点

在数据元素的非空有限集中:

  • 存在唯一的一个被称作“第一个”的数据元素

  • 存在唯一的一个被称作“最后一个”的数据元素

  • 除第一个数据元素外,集合中的每个数据元素均只有一个前驱

  • 除最后一个数据元素外,集合中的每个数据元素均只有一个后继

线性表的逻辑结构

线性表的定义

一个线性表是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).nextp->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;
 }

猜你喜欢

转载自blog.csdn.net/Keikei419/article/details/127196918
今日推荐