线性表是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
1.数组形式---顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。顺序表一般可以分为:静态顺序表(使用定长数组存储)、 动态顺序表(使用动态开辟的数组存储)
// 静态顺序表
#define N 10
typedef int DataType;
typedef struct SeqList {
DataType arr[N];
int size; // 有效数据个数
} List;
// 动态顺序表
typedef int DataType;
typedef struct SeqList {
DataType* arr; // 指向堆上申请的空间地址
int capacity; // 数组空间容量
int size; // 有效数据个数
} List;
2.接口的实现
#pragma once
// 常见的线性表:顺序表、链表、栈、队列、字符串...
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
/*
**********动态顺序表
**********可以随机访问,
*/
typedef int DataType;
typedef struct SeqList {
DataType* arr; // 指向堆上申请的空间地址
int capacity; // 数组空间容量
int size; // 有效数据个数
} List;
// 初始化
void Init(List* SeqList);
// 销毁
void Destory(List* SeqList);
// 插入
void PushFront(List* SeqList, DataType value);
void PushBack(List* SeqList, DataType value);
void Insert(List* SeqList, DataType value, int pos);
// 删除
void PopFront(List* pSeqList);
void PopBack(List* pSeqList);
void Erase(List* pSeqList, int pos);
// 扩容
void CheckCapacity(List* pSeqList);
// 查找,找到了返回下标,没找到返回-1
int Search(List* pSeqList, DataType value);
// 修改
void Modify(List* pSeqList, int pos, DataType value);
// 移除(所有)==value的元素
void Remove(List* pSeqList, DataType value);
void RemoveAll(List* pSeqList, DataType value);
// 容量
int Capaciyt(List* pSeqList);
// 判断是否为空
int IsEmpty(List* pSeqList);
// 返回顺序表的首尾元素
DataType Front(List* pSeqList);
DataType Back(List* pSeqList);
#include "SeqList.h"
// 需要对结构体进行修改,所以参数需要传址调用
void Init(List* pSeqList) {
assert(pSeqList->arr != NULL);
pSeqList->capacity = 6; // 设置容量
pSeqList->arr = (DataType*)malloc
(sizeof(DataType) * pSeqList->capacity); // 创建数组空间
if (pSeqList->arr == NULL) {
printf("数组动态内存分配失败");
return;
}
pSeqList->size = 0; // 有效元素为0个
return;
}
void Destory(List* pSeqList) {
assert(pSeqList != NULL);
free(pSeqList->arr); // 释放动态申请的内存,顺序表结构体成员arr空间
pSeqList->arr = NULL;
pSeqList->size = 0; // 清除有效元素个数
pSeqList->capacity = 0; // 容量置空
return;
}
void PushFront(List* pSeqList, DataType value){
assert(pSeqList != NULL);
CheckCapacity(pSeqList);
for (int i = pSeqList->size; i > 0; i--) {
// 将所有元素后移一位
pSeqList->arr[i] = pSeqList->arr[i - 1];
}
pSeqList->arr[0] = value;
pSeqList->size++;
return;
}
void PushBack(List* pSeqList, DataType value) {
assert(pSeqList != NULL);
CheckCapacity(pSeqList);
pSeqList->arr[pSeqList->size] = value;
pSeqList->size++;
}
void Insert(List* pSeqList, DataType value, int pos) {
assert(pSeqList != NULL);
CheckCapacity(pSeqList);
if (pos < 0 && pos >= pSeqList->size) {
return; // 插入位置不能小于零且不能大于size
}
for (int i = pSeqList->size; i > pos; i--) {
pSeqList->arr[i] = pSeqList->arr[i - 1];
}
pSeqList->arr[pos] = value;
pSeqList->size++;
}
void PopFront(List* pSeqList) {
assert(pSeqList != NULL);
if (pSeqList->size > 0 ) {
for (int i = 0; i < pSeqList->size; i++) {
pSeqList->arr[i] = pSeqList->arr[i + 1];
}
pSeqList->size--;
}
return;
}
void PopBack(List* pSeqList) {
assert(pSeqList != NULL);
if (pSeqList->size > 0) {
pSeqList->arr[pSeqList->size - 1] = -1;
pSeqList->size--;
}
return;
}
void Erase(List* pSeqList, int pos) {
assert(pSeqList != NULL);
if (pos < 0 && pos >= pSeqList->size) {
return;
}
for (int i = pos; i < pSeqList->size; i++) {
pSeqList->arr[i] = pSeqList->arr[i + 1];
}
pSeqList->size--;
}
// 扩容!
void CheckCapacity(List* pSeqList) {
assert(pSeqList != NULL);
if (pSeqList->size < pSeqList->capacity) {
return;
}
int newcapacity = pSeqList->capacity * 2;
DataType* newarr =
(DataType*)malloc(sizeof(DataType) * newcapacity);
if (newarr == NULL) {
printf("内存分配失败");
return;
}
for (int i = 0; i < pSeqList->size; ++i) {
newarr[i] = pSeqList->arr[i];
}
for (int i = pSeqList->size - 1; i >= 0; --i) {
newarr[i] = pSeqList->arr[i];
}
free(pSeqList->arr);
pSeqList->arr = newarr;
pSeqList->capacity = newcapacity;
}
int Search(List* pSeqList, DataType value) {
assert(pSeqList != NULL);
for (int i = 0; i < pSeqList->size; i++) {
if (pSeqList->arr[i] == value) {
return i;
}
}
return -1;
}
void Modify(List* pSeqList, int pos, DataType value) {
assert(pSeqList != NULL);
if (pos < pSeqList->size) {
pSeqList->arr[pos] = value;
}
return;
}
void Remove(List* pSeqList, DataType value) {
assert(pSeqList != NULL);
int pos = Search(pSeqList, value); // 查找==value的下标
Erase(pSeqList, pos);
return;
}
void RemoveAll(List* pSeqList, DataType value) {
assert(pSeqList != NULL);
int i, j;
for (i = 0, j = 0; i < pSeqList->size; i++) {
if (pSeqList->arr[i] != value) {
// 双指针遍历,下标为i的元素不为value,就赋值给下标为j的元素
// 反之,j不用,i继续往后走
pSeqList->arr[j] = pSeqList->arr[i];
j++;
}
}
pSeqList->size = j; // 更新顺序表的有效个数
return;
}
int Capaciyt(List* pSeqList) {
return pSeqList->capacity;
}
int IsEmpty(List* pSeqList) {
if (pSeqList->size == 0) {
return 1;
}
return 0;
}
DataType Front(List* pSeqList) {
return pSeqList->arr[0];
}
DataType Back(List* pSeqList) {
return pSeqList->arr[pSeqList->size - 1];
}
#include "SeqList.h"
int main() {
List SeqList; // 定义一个顺序表结构体变量
Init(&SeqList);
PushBack(&SeqList, 1);
PushBack(&SeqList, 2);
PushBack(&SeqList, 3);
PushBack(&SeqList, 4);
PushBack(&SeqList, 5);
PushBack(&SeqList, 6);
PushBack(&SeqList, 44);
int num = Search(&SeqList, 5);
RemoveAll(&SeqList, 2);
Destory(&SeqList);
system("pause");
return 0;
}
3.顺序表的优缺点
优点:存取速度高效,通过下标来直接存储
缺点:.插入和删除比较慢、数据溢满需要扩容
增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
1.链式结构---链表
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
2.链表的分类:
无头单向非循环链表、无头单向循环链表、无头双向非循环链表、无头双向循环链表、
带头单向非循环链表、带头单向循环链表、带头单向非循环链表、带头双向循环链表、
常用的有两种:
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
接口实现:
#pragma once
//
// 无头单向链表
//
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
/*
节点结构体
*/
typedef int DataType;
typedef struct Node {
struct Node* next; // 指针域
DataType value; // 数据域
} Node;
// 初始化
void Init(Node* rootp);
// 销毁
void Destory(Node* rootp);
// 头插,尾插
void PushFront(Node* rootp, DataType value);
void PushBack(Node* rootp, DataType value);
void Insert(Node* rootp, Node* pos, DataType value);
// 头删、尾删
void PopFront(Node* rootp);
void PopBack(Node* rootp);
void Erase(Node* rootp, Node* pos);
// 查找指定节点,找到返回该节点地址、否则返回空
Node* Search(Node* rootp, DataType value);
// 计算节点个数
int ListSize(Node* rootp);
int Empty(Node* rootp);
// 获取链表的第一个节点
Node* FirstNode(Node* rootp);
// 获取链表的第二个节点
Node* SecondNode(Node* rootp);
// 删除链表中第一个value节点
Node* Remove(Node* rootp, DataType value);
// 删除链表中的所有value节点
Node* RemoveAll(Node* rootp, DataType value);
#include "List.h"
void Init(Node* rootp) {
// 参数是根节点指针
rootp->next = NULL;
return;
}
/*
rootp为根节点指针
rootp->next是根节点
rootp->next->next是第二个节点
*/
// 头插-> O(1)
void PushFront(Node* rootp, DataType value) {
assert(rootp != NULL);
// 创建一个新节点
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL) {
printf("内存申请失败");
return;
}
node->value = value;
// rootp->next 为根节点, 链表为空则为NULL
node->next = rootp->next;
rootp->next = node;
return;
}
void PushBack(Node* rootp, DataType value) {
assert(rootp != NULL);
Node* cur = rootp; // 保存根节点指针
while (cur->next != NULL) {
// cur->next为NULL,表示没有节点
cur = cur->next;
} // 循环退出后,cur指向最后一个节点
// 创建新节点
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL) {
printf("内存申请失败");
return;
}
node->value = value;
node->next = NULL; // 尾插,所以指针域为空
// 插入
cur->next = node;
return;
}
void PopFront(Node* rootp) {
assert(rootp != NULL); // 链表存在
assert(rootp->next != NULL); // 空链表
/*
malloce申请的内存需要free掉,不然会造成内存泄漏
先free掉根节点,不能找到第二个节点
先将根节点指针指向第二个节点,这时候free的就是第二个节点
解决方法:将第二个节点存储下来,再free掉根节点
*/
// 只有一个节点时,rootp->next->next(第二个节点)为空
Node* node = rootp->next->next;
free(rootp->next);
rootp->next = node;
return;
}
void PopBack(Node* rootp) {
assert(rootp != NULL);
assert(rootp->next != NULL); // 空链表
Node* cur = rootp; // 保存根节点指针
// 只有一个节点时,cur->next->next为空,while不执行
while (cur->next->next != NULL) {
cur = cur->next;
} // 循环退出后,cur指向倒数第二个节点
free(cur->next);
cur->next = NULL;
return;
}
void Insert(Node* rootp, Node* pos, DataType value) {
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL) {
printf("内存动态申请失败");
return;
}
node->value = value;
Node* tmp = pos->next;
pos->next = node;
node->next = tmp;
return;
}
void Erase(Node* rootp, Node* pos) { // 删除pos后面的哪个节点
assert(rootp->next != NULL);
Node* tmp = pos->next->next;
free(pos->next);
pos->next = tmp;
}
Node* Search(Node* rootp, DataType value) {
assert(rootp != NULL);
Node* cur = rootp;
// 1-2-3-4-5-n
while (cur != NULL) {
if (cur->value == value) {
return cur;
}
cur = cur->next;
} // 循环结束cur指向等于value的节点
return NULL;
}
int ListSize(Node* rootp) {
if (rootp == NULL) {
return 0;
}
int count = 0;
Node* cur = rootp;
while(cur != NULL){
count++;
cur = cur->next;
}
return count;
}
int Empty(Node* rootp) {
if (rootp == NULL) {
return 1;
}
return 0;
}
Node* FirstNode(Node* rootp) {
if (rootp == NULL) {
return NULL;
}
return rootp;
}
Node* SecondNode(Node* rootp){
if (rootp == NULL) {
return NULL;
}
return rootp->next;
}
Node* Remove(Node* rootp, DataType value) {
assert(rootp != NULL); // 表示链表存在
Node* pre = NULL;
Node* cur = rootp;
Node* tmp = NULL;
while (cur != NULL) {
if (cur->value == value) {
break;
}
pre = cur;
cur = cur->next;
}
if (pre != NULL && cur != NULL) {
pre->next = cur->next;
free(cur);
}
return rootp;
}
// 删除链表中的所有value节点
Node* RemoveAll(Node* rootp, DataType value) {
if (rootp == NULL) { return NULL; }
while (rootp != NULL && rootp->value == value) {
Node* tmp = rootp->next;
free(rootp);
rootp = tmp;
}
Node* p = NULL;
Node* q = NULL;
p = q = rootp;
while (p != NULL) {
if (p->value == value) {
q->next = p->next;
free(p);
p = q->next;
}
else {
q = p;
p = p->next;
}
}
return rootp;
}
void Destory(Node* rootp) {
assert(rootp != NULL);
Node* next = NULL;
Node* cur = rootp;
while (cur != NULL) {
next = cur->next;
free(cur);
cur = next;
}
rootp = NULL;
}
带头双向循环链表:
结构最复杂,每个节点除了具有数据域外,还有两个指针域,分别指向前驱节点和后继节点。从双向链表中的任一结点开始,均可方便地访问其前驱结点和后继结点。双向链表通常含有一个表头结点,亦称哨兵结点(Sentinel Node)。
实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单,后面我们代码实现了就知道了。
接口实现:
#pragma once
#include <stdio.h>
#include <assert.h>
#include <malloc.h>
/*
带头双向循环链表
*/
typedef char DataType;
typedef struct DNode {
DataType value; // 数据域
struct DNode* pre; // 前驱节点指针域
struct DNode* next; // 后继节点指针域
} DNode;
// 初始化
void Init(DNode** rootp);
// 销毁
void Destroy(DNode* rootp);
// 插
void PushFront(DNode* head, DataType value);
void PushBack(DNode* head, DataType value);
void InsertAfter(DNode* head, DNode* pos, DataType value);
void InsertBefore(DNode* head, DNode* pos, DataType value);
// 删
void PopFront(DNode* head);
void PopBack(DNode* head);
void Erase(DNode* head, DNode* pos);
DNode* Find(DNode* rootp, DataType value);
DNode* Clear(DNode* rootp);
#include "DList.h"
void Init(DNode** rootp) {
// 参数为指向根节点指针的指针
assert(rootp != NULL);
// 动态申请一段内存用来存储头节点、将这段地址赋给根节点指针head
DNode* head = (DNode*)malloc(sizeof(DNode));
if (head == NULL) {
printf("动态内存申请失败");
return;
}
head->next = head;
head->pre = head;
*rootp = head;
}
// head->next 为根节点
void PushFront(DNode* head, DataType value) {
assert(head != NULL); // 保证链表存在
DNode* node = (DNode*)malloc(sizeof(DNode));
if (node == NULL) {
printf("动态内存申请失败");
return;
}
node->value = value;
// 知道根节点
node->pre = head->pre;
node->next = head;
head->pre->next = node;
head->pre = node;
}
// head->pre 为尾节点
void PushBack(DNode* head, DataType value) {
assert(head != NULL); // 保证链表存在
DNode* node = (DNode*)malloc(sizeof(DNode));
if (node == NULL) {
printf("动态内存申请失败");
return;
}
node->value = value;
// 知道根节点
node->pre = head->pre;
node->next = head;
head->pre->next = node;
head->pre = node;
}
void InsertAfter(DNode* head, DNode* pos, DataType value) {
assert(head != NULL); // 保证链表存在
DNode* node = (DNode*)malloc(sizeof(DNode));
if (node == NULL) {
printf("动态内存申请失败");
return;
}
node->value = value;
node->pre = pos;
node->next = pos->next;
pos->next->pre = node;
pos->next = node;
}
void InsertBefore(DNode* head, DNode* pos, DataType value) {
assert(head != NULL); // 保证链表存在
DNode* node = (DNode*)malloc(sizeof(DNode));
if (node == NULL) {
printf("动态内存申请失败");
return;
}
node->value = value;
node->pre = pos->pre;
node->next = pos;
pos->pre->next = node;
pos->pre = node;
}
void PopFront(DNode* head) {
assert(head != NULL);
DNode** rootp = &head;
DNode* tmp = head->next; // 保存第二个节点
head->next->pre = head->pre;
head->pre->next = head->next;
free(head);
*rootp = tmp;
}
void PopBack(DNode* head) {
assert(head != NULL); // 保证链表存在
DNode* tmp = head->pre; // 保存倒数第二个节点
head->pre = head->pre->pre;
head->pre->next = head;
free(tmp);
}
void Erase(DNode* head, DNode* pos) {
assert(head != NULL); // 保证链表存在
pos->pre->next = pos->next;
pos->next->pre = pos->pre;
free(pos);
}
void Destroy(DNode* head) {
assert(head != NULL);
DNode** rootp = &head;
DNode* cur = head->next;
while (cur != NULL && cur != head) {
DNode* tmp = cur;
cur = cur->next;
free(tmp);
}
free(cur);
*rootp = NULL;
}
#include "DList.h"
DNode* CreateNode(DataType value) {
DNode* node = (DNode*)malloc(sizeof(DNode));
if (node == NULL) {
printf("内存分配失败");
return NULL;
}
node->value = value;
node->pre = NULL;
node->next = NULL;
return node;
}
int main() {
DNode* A = CreateNode('A');
DNode* B = CreateNode('B');
DNode* C = CreateNode('C');
DNode* D = CreateNode('D');
A->next = B; B->next = C; C->next = D; D->next = A;
B->pre = A; C->pre = B; D->pre = C; A->pre = D;
DNode* head = A;
//PushFront(head, 'K');
PushBack(head, 'R');
//InsertAfter(head, B, 'M');
//InsertBefore(head, C, 'N');
//PopFront(head);
//PopBack(head);
//Erase(head, C);
Destroy(head);
return 0;
}