线性表
1、线性表的定义
线性表简称为表,是由n(n>=0)个数据元素(也叫节点或表元素)组成的有限序列。n=0时,该线性表成为空表。
表中各数据元素: 1)存在线性关系 2)结构类型完全一致
2、线性表的特点
- 唯一首元素
- 唯一尾元素
- 除首元素外,任何一个元素都有一个前驱
- 除尾元素外,任何一个元素都有一个后继
- 每个元素有一个位序(下标)
线性表的顺序存储
1.线性表的顺序存储原理
用一组地址连续的存储单位按线性表元素之间的逻辑顺序,一次存储线性表的数据元素。(属于静态存储,不能自由扩充)
数据元素的逻辑顺序和物理上的存储顺序完全一致,因此不需要另外建立空间来记录各个元素间
的关系
2、优、缺点
优点:随机存储。可以随机存取表中的任意一个数据元素(查快)
缺点:存储密度大。在插入、删除某元素时,需要移动大量元素(改慢);浪费储存空间
代码实现
#include<stdio.h>
#include<stdlib.h>
#define INIT_SIZE 10 //初始值
#define INCERMENT 5 //增加值
typedef int ElemType; //自定义类型 (别名)
typedef struct {
ElemType* elem; //元素首地址
int length; //长度(元素个数)
int listsize; //容量(内存大小),用以判断顺序表是否为空
}SqList; //顺序表名
//初始化
int Init_Sqlist(SqList* L) {
L->elem = (ElemType*)malloc(INIT_SIZE * sizeof(ElemType)); //分配长度为INIT_SIZE字节的内存块
if (!L->elem) //if(L->elem == NULL)
return -1;
L->length = 0;
L->listsize = INIT_SIZE;
return 0;
}
//插入 , 在第i(下标)个位置插入元素e
int Insert_SqList(SqList* L, int i, ElemType e) {
ElemType* newBase; //保存新分配的内存首地址
int j;
if (i<0 || i>L->length) //判断i的值是否有效
return -1;
if (L->length >= L->listsize) { //判断内存是否满
newBase = (ElemType*)realloc(L->elem , (L->listsize + INCERMENT) * sizeof(ElemType)); //将malloc()函数分配的地址扩大
if (newBase == NULL)
return -1;
L->elem = newBase;
L->listsize += INCERMENT;
}
for (j = L->length - 1; j >= i; j--){ //i下标后的值后移
L->elem[j + 1] = L->elem[j];
}
L->elem[i] = e;
L->length++;
return 0;
}
//查找 , 找到值为e的位置
int Find_SqList(SqList L, ElemType e) {
for (int i = 0; i < L.length ; i++) {
if (L.elem[i] == e)
return i + 1;
}
return 0;
}
//删除 , 第i(下标)位置的值
int Delect_SqList(SqList* L, int i) {
if (i<0 || i>L->length)
return -1;
for (int j = i; j < L->length; j++) //将i位置后的值前移,覆盖i位置的值
L->elem[j] = L->elem[j + 1];
L->length--;
return 0;
}
//输出
void show(SqList L) {
for (int i = 0; i < L.length; i++) {
printf("%d\t", L.elem[i]);
}
printf("\n");
}
void main() {
SqList L;
Init_Sqlist(&L);
Insert_SqList(&L, 0, 1);
Insert_SqList(&L, 1, 2);
Insert_SqList(&L, 2, 3);
show(L);
int a = Find_SqList(L, 3);
printf("%d", a);
printf("\n");
Delect_SqList(&L, 1);
show(L);
system("pause");
}
线性表的链式存储
1.线性表的链式存储原理
用一组任意的存储单位来存放线性表的数据元素(存储单位可以是连续的,也可以是不连续的),数据元素之间的逻辑关系通过指针来指示。其中包括单链表、循环链表、双向链表。
2、优、缺点
优点:插入、删除通过修改链表指针,不需要移动数据元素(改快)
缺点:不可随机存取元素(查慢)
1、单链表
1)单链表存储原理
对于每一个数据元素来说,链表除了存放数据元素本身的值以外,还应一起存放数据元素的直接后继点所在存储单元的内存起始地址。
每个节点只有一个指向后继节点的指针。
每个节点包括数据域和指针域才可将数据元素之间的逻辑关系完全体现出来,(节点间的逻辑
结构比每个节点的实际内存地址显得更重要)
2) 头节点
定义:在链表头部加入一个特殊的节点,类型与数据节点相同,但其数据域不存放有效值,标识链表的头指针变量指向该节点。
作用:链表为空时头节点的头指针就指向头节点(头节点指针域为NULL);在插入和删除操作中无需进行特殊处理。
代码实现
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
typedef struct LNode { //结点
ElemType data; //数据域
struct LNode *next; //指针域
}LNode, *LinkList; //LNode* 等价于LinkList
//根据元素的值生成一个结点
LNode *CreateNode(ElemType e) {
LNode *p = (LNode*)malloc(sizeof(LNode));
p->data = e;
p->next = NULL;
return p;
}
//初始化
LinkList Init_LinkList() {
return CreateNode(0);
}
//插入 , 在某结点curNode后插入新结点newNode
//当curNode为NULL时,表示在尾部插入
void Insert_LinkList(LinkList L, LNode* curNode, LNode* newNode) {
LNode* p = curNode;
if (!newNode) {
return;
}
if (!curNode) { //在尾部插入
p = L; //p为头结点
while (p->next != NULL) {
p = p->next; //下一个
}
}
newNode->next = p->next; //插入新结点
p->next = newNode;
L->data++;
}
//查找 , 元素所在的结点位序(下标)
LNode* Find_LinkList(LinkList L, ElemType e) {
LNode* p = L->next; //首元
int i = 0;
while (p != NULL) {
if (p->data == e)
return i;
p = p->next;
i++;
}
return NULL;
}
//删除 , 按元素删除
void Delete_LinkList(LinkList L, ElemType e) {
LNode* p, * c; //p为前一个结点,c为当前结点
p = L;
while (p->next != NULL) {
if (p->next->data == e) {
c = p->next;
p->next = p->next->next;
L->data--;
free(c);
break;
}
p = p->next;
}
}
//遍历
void show(LinkList L) {
LNode* p; //链表首元
for (p = L->next; p != NULL; p = p->next) {
printf("%d\t", p->data);
}
printf("\n");
}
void main() {
LinkList L = NULL;
LNode* node1, * node2, * node3, * node4, * node5;
L = Init_LinkList();
if (L == NULL)
printf("链表初始化失败");
//生成结点
node1 = CreateNode(1);
node2 = CreateNode(2);
node3 = CreateNode(3);
node4 = CreateNode(4);
node5 = CreateNode(5);
//尾部插入
Insert_LinkList(L, NULL, node1);
Insert_LinkList(L, NULL, node2);
Insert_LinkList(L, node1, node3);
//最前面插入
Insert_LinkList(L, L, node4);
Insert_LinkList(L, node2, node5);
show(L);
int i = Find_LinkList(L, 3);
printf("%d", i);
printf("\n");
Delete_LinkList(L, 5);
show(L);
}
2、循环链表
将单链表的尾结点的指针域指向头节点。
特性:从任何一个节点开始,都可以遍历整个链表。(单链表:整个链表遍历需从头节点开始)
代码实现
//初始化
LinkList Init_LinkList() {
LinkList head = CreateNode(0);
head->next = head;
return head;
}
//插入 , 在某结点xNode后插入新结点newNode
//当xNode为NULL时,表示在尾部插入
void Insert_LinkList(LinkList head, LNode* xNode, LNode* newNode) {
LNode* tail;
LNode* p = head;
while(p->next != head) {
p = p->next;
}
tail = p;
if (xNode == NULL || xNode == tail) { //在尾部插入
tail->next = newNode;
newNode->next = head;
}else {
newNode->next = xNode->next;
xNode->next = newNode;
}
head->data++;
}
3、双向链表
在链表节点中增加一个指向前驱节点的指针。
特性:可以快速找到节点前驱和后继
双向链表的储存结构描述
typedef struct DuLNode{
ElemType data;
struct DuLNode *prior; //指向前驱
struct DuLNode *next; //指向后继
}DuLNode,* DuLinkList;
双向循环链表
双向链表中每个节点都有指向前驱和后继的指针,所以在插入、删除时对指针的操作必须在两个方向同时进行,否则可能会使其中一条链被截断(若截断可以利用另一条链来修补)。
因此,为了算法统一将双向链表的前向单链表和后向单链表共用一个头结点首尾相连,构成一个双向循环链表。
双向循环链表的某节点后插入
void inselem(DuLinkList L,ElemType e,DuLNode *p){
DuLNode *s,*r;
s = (DULNode*)malloc(sizeof(DuLNode));
s->data = e; //建立一个新节点s
r = p->next;
s->next = r; //s的后继为r
r->prior = s; //插入节点p后
p->next = s;
s->prior = p;
}
在某节点前插入类似
双向循环链表的删除某节点
删除某节点,即是修改某节点前节点的next指针、后节点的prior指针,可以创建一个指针返回
删除的节点,最后释放删除节点空间(free())