数据结构 —— 线性表的定义、存储结构和基本操作
什么是线性表
- 线性表是具有相同特征数据元素的一个有限序列。该序列中所含元素的个数叫作线性表的长度,用 n 表示,当 n = 0 时,表示线性表是一个空表。
- 线性表可以是有序的,也可以是无序的
- 线性表的存储结构有顺序存储结构和链式存储结构两种。前者称为顺序表,后者称为链表。
顺序表
顺序表就是把线性表中的所有元素按照其逻辑顺序,依次存储到从指定的存储位置开始的一块连续的存储空间中。
顺序表的特性:
- 随机访问特性:只要知道点的位置,就可以直接访问
- 占用连续的存储空间:存储分配只能预先进行,一旦分配好了,在对其操作的过程中始终不变
- 顺序表做插入操作的时候要移动多个元素
- 具有 n 个元素的顺序表,插入一个元素的平均移动个数为 (n-1)/2
- 具有 n 个元素的顺序表,查找一个元素的平均移动个数为 (n+1)/2
结构体定义:
// 结构体定义 #define maxSize 100 typedef struct { int data[maxSize]; int length; }Sqlist;
顺序表初始化:
// 顺序表初始化 void initList(Sqlist &L) { L.length=0; }
查找算法(下标):在顺序表L中查找下标为p的元素
// 查找算法(下标):在顺序表L中查找下标为p的元素 int getElem(Sqlist L,int p) { if(p<0||p>L.length-1) return -1; return L.data[p]查找算法(值):在顺序表L中查找第一个值等于e的元素; // 若找到,则返回对应的元素的数值 }
// 查找算法(值):在顺序表L中查找第一个值等于e的元素 int findElem(Sqlist L,int e) { int i; for(i=0;i<L.length;i++) { if(e==L.data[i]) return i; // 若找到,则返回下标 } return -1; // 若未找到,则返回-1作为错误标志 }
插入算法:在顺序表L的第p个位置插入新的元素e
// 插入算法:在顺序表L的第p个位置插入新的元素e int insertElem(Sqlist &L,int p,int e) { int i; if(p<0||p>L.length||L.length==maxSize) return -1; //插入失败,返回-1 for(i=L.length-1;i>=p;i--) { L.data[i+1]=L.data[i]; } L.data[p]删除算法:删除顺序表L的第p个位置的元素=e; L.length++; return 0; //插入成功,返回0 }
// 删除算法:删除顺序表L的第p个位置的元素 int delElem(Sqlist &L,int p) { if(p<0||p>L.length-1) return -1; int i; for(i=p;i<L.length-1;i++) L.data[i]=L.data[i+1]; L.length--; return 0; }
链表
在链表存储中,每个结点不仅包含所存元素的信息,还包含元素之间逻辑关系的信息,如单链表中前驱结点包含后继结点的地址信息,这样就可以通过前驱结点中的地址信息找到后继结点的位置。
链表的特性:
- 不支持随机访问
- 支持存储空间的动态分配
- 链表中进行插入操作无须移动元素
单链表
在每个结构中除了包含数据域外,还包含一个指针域,用以指向其后继结点。
- 带头结点的单链表中,头指针 head 指向头结点,头结点的值域不包含任何信息,从头结点的后继结点开始存储数据信息。头指针 head 始终不等于 NULL,head -> next 等于 NULL 的时候,链表为空。
- 不带头结点的单链表中的头指针 head 直接指向开始结点,当 head 等于 NULL 的时候,链表为空。
两者最明显的区别是,带头结点的的单链表中有一个结点不存储信息,只是作为标志,而不带头结点的单链表的所有结点都存储信息。
结构体定义
// 结构体定义 typedef struct LNode { int data; struct LNode *next; }LNode;
查找算法
// 查找算法:在单链表L中查找值为e的元素(带头结点) LNode* findElem(LNode *C,int e) { LNode *p=< 1c6f4 /span>C->next; // 结构体是指针,访问时用-> while(p!=NULL) { if(p->data==e) return p; // 返回结点指针 p=p->next; } return p; // 返回NULL }
插入算法
// 插入算法:往结点p的后面插入结点s void insertElem(LNode *&p,LNode *&s) { s->next=p->next; p->next=s; }
删除算法
// 删除算法:删除结点p后面的结点 void delElem(LNode *&p) { q=p->next; p->next=q->next; free(q); }
尾插法建立链表
// 尾插法建立链表:假设有n个元素存储在数组a中,用尾插法建立链表C void createListR(LNode *&C,int a[],int n) // 因为需要对链表C进行修改,故要加上&表示引用 { C=(LNode *)malloc(sizeof(LNode)); // 申请C的头结点空间 C->next=NULL; int i; LNode *s,*r; // s用来指向新申请的结点,r用来指向链表C的尾结点 r=C; for(i=0;i<n;i++) { s=(LNode *)malloc(sizeof(LNode)); s->data=a[i]; r->next=s; r=r->next; // 由于r是指针(地址),故修改r也相当于修改了C } r->next=NULL; }
头插法建立链表
// 头插法建立链表:假设有n个元素存储在数组a中,用头插法建立链表C void createListR(LNode *&C,int a[],int n) // 因为需要对链表C进行修改,故要加上&表示引用 { C=(LNode *)malloc(sizeof(LNode)); // 申请C的头结点空间 C->next=NULL; int i; LNode *s; // s用来指向新申请的结点 for(i=0;i<n;i++) { s=(LNode *)malloc(sizeof(LNode)); s->data=a[i]; s->next=C->next; // s所指新结点的指针域 next 指向C中的开始结点 C->next=s; // 头结点的指针域 next 指向s结点,使得s成为新的开始结点 } }
单链表的归并
// 单链表的归并:A和B是两个单链表(带表头结点),其中元素递增有序。设计一个算法,将A和B归并成一个按元素值 // 非递减有序的链表C,C由A和B中的结点组成 void merge(LNode *A,LNode *B,LNode *&C) { LNode *p=A->next; // 指针p指向链表A的开始结点(最小值结点) LNode *q=B->next; LNode *r; // r始终指向C的终端结点 C=A; // 用A的头结点作为C的头结点 C->next=NULL; free(B); r=C; while(p!=NULL&&q!=NULL ) { if(q->data>=p->data) { r->next=p; p=p->next; r=r->next; } else { r->next=q; q=q->next; r=r->next; } } r->next=NULL; if(p!=NULL)r->next=p; if(q!=NULL)r->next=q; }
双链表
在每个结构中除了包含数据域外,还包含两个指针域,用以分别指向其后继结点和前驱结点。同样,双链表也分为带头结点的双链表和不带头结点的双链表,情况类似于单链表。
- 带头结点的双链表,当 head ->next 为 NULL 时链表为空
- 不带头结点的双链表,当 head 为 NULL 时链表为空
结构体定义
// 结构体定义 typedef struct DLNode { int data; struct DLNode *next; struct DLNode *prior; }DLNode;
查找算法
// 查找算法:在双链表L中查找值为e的元素(带头结点) DLNode* findElem(DLNode *C,int e) { DLNode *p=C->next; // 结构体是指针,访问时用-> while(p!=NULL) { if(p->data==e) return p; // 返回结点指针 p=p->next; } return p; // 返回NULL }
插入算法
// 插入算法:往结点p的后面插入结点s void insertElem(DLNode *&p,DLNode *&s) { s->next=p->next; s->prior=p; p->next->prior=s; p->next=s; }
删除算法
// 删除算法:删除结点p后面的结点 void delElem(DLNode *&p) { q=p->next; p->next=q->next; q->next->prior=p; free(q); }
尾插法建立链表
// 尾插法建立链表:假设有n个元素存储在数组a中,用尾插法建立链表C void createDlistR(LNode *&C,int a[],int n) // 因为需要对链表C进行修改,故要加上&表示引用 { C=(LNode *)malloc(sizeof(LNode)); // 申请C的头结点空间 C->next=NULL; int i; DLNode *s,*r; // s用来指向新申请的结点,r用来指向链表C的尾结点 r=C; for(i=0;i<n;i++) { s=(DLNode *)malloc(sizeof(DLNode)); s->data=a[i]; r->next=s; s->prior=r; // 与单链表不同之处 r=r->next; // 由于r是指针(地址),故修改r也相当于修改了C } r->next=NULL; }
循环单链表
只要将单链表的终端结点的 next 指针指向链表中的第一个结点(头结点或开始结点)即可。
- 循环单链表可以实现从任意一个结点出发访问链表中的任何结点,而单链表从任一结点出发后只能访问这个结点本身及其后边的所有结点。
- 带头结点的循环单链表,当 head ->next 为 NULL 时链表为空
- 不带头结点的双链表,当 head 为 NULL 时链表为空
循环双链表
将双链表的终端结点的 next 指针指向链表中的第一个结点(头结点或开始结点),将双链表的第一个结点的 prior 指针指向链表中的终端结点。
- 带头结点的循环单链表,当 head ->next == head 或 head ->prior == head 时链表为空
- 不带头结点的双链表,当 head 为 NULL 时链表为空
静态链表
-
一般链表结点空间来自于整个内存,静态链表则来自于一个结构体数组。
-
数组中的每一个结点含有两个分量:一个是数据元素分量 data ,另一个是指针分量,指示了当前结点的直接后继结点在数组中的位置。
-
静态链表中的指针不是我们通常所说的C语言中用来存储内存地址的指针型变量,而是一个存储数组下标的整型变量,通过它可以找到后继结点在数组中的位置,其功能类似于真实的指针,因此称其为指针。
- 数据结构 —— 队列的定义、存储结构和基本操作
- 数据结构 —— 栈的定义、存储结构和基本操作
- 数据结构(一)顺序表1:顺序存储的基本操作
- 数据结构2:线性表的构建和基本操作
- 数据结构3:检验线性表的基本操作和线性表的合并
- 数据结构中线性表的基本操作-合并两个线性表-按照元素升序排列
- 数据结构_图_定义/分类/顶点与边之间的关系/连通图/存储结构/基本操作
- 线性表链式存储结构下基本操作的实现(初始化、赋值、取值、插入、删除、归并等)
- bo3-1-1.c 链栈(存储结构由c2-2.h定义)的基本操作(4个) 及验证
- 爹地的实验:实验一: 数据结构实验一线性表的基本操作实现及其应用
- [C++]数据结构:线性表的公式化描述和链式描述的结构特点与基本操作
- 数据结构笔记(二)线性表的链式表示和基本操作
- bo2-1.cpp 顺序表示的线性表(存储结构由c2-1.h定义)的基本操作(12个)
- bo2-2.cpp 带有头结点的单链表(存储结构由c2-2.h定义)的基本操作(12个)
- 线性表数据结构类型定义及相关操作总结
- C++数据结构之线性顺序表基本操作
- bo2-4.cpp设立尾指针的单循环链表(存储结构由c2-2.h定义)的12个基本操作
- 数据结构之线性表顺序存储的常用操作(转载)
- bo2-5.cpp 带头结点的双向循环链表(存储结构由c2-4.h定义)的基本操作(14个)
- 数据结构-顺序存储-线性表-基本运算