一、线性表
1.定义:具有相同特性的数据元素的一个有限序列
2.特性:有穷性、一致性、序列性
二、线性表的顺序存储结构----顺序表
顺序表的各种操作(含详细注释)
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 50
typedef int ElemType; // 假设元素类型为int
typedef struct {
ElemType data[MAXSIZE]; // 存放线性表中的元素
int length; // 存放线性表的长度
} SqList; // 顺序表类型
// 1. 初始化顺序表
void InitList(SqList *L) {
L->length = 0;
}
// 2. 销毁顺序表(顺序表是静态分配的,不需要特殊操作)
void DestroyList(SqList *L) {
L->length = 0; // 只需将长度置0
}
// 3. 判断顺序表是否为空
int ListEmpty(SqList L) {
return (L.length == 0);
}
// 4. 求顺序表长度
int ListLength(SqList L) {
return L.length;
}
// 5. 按元素值查找,返回位置(从1开始),找不到返回0
int LocateElem(SqList L, ElemType e) {
for (int i = 0; i < L.length; i++) {
if (L.data[i] == e) {
return i + 1; // 返回位置(从1开始)
}
}
return 0; // 未找到
}
// 6. 插入元素(在位置i插入元素e)
int ListInsert(SqList *L, int i, ElemType e) {
if (i < 1 || i > L->length + 1) { // 检查插入位置是否合法
return 0; // 插入失败
}
if (L->length >= MAXSIZE) { // 检查顺序表是否已满
return 0; // 插入失败
}
// 将第i个位置及之后的元素后移
for (int j = L->length; j >= i; j--) {
L->data[j] = L->data[j - 1];
}
L->data[i - 1] = e; // 插入新元素
L->length++; // 表长加1
return 1; // 插入成功
}
// 7. 删除元素(删除位置i的元素,并用e返回其值)
int ListDelete(SqList *L, int i, ElemType *e) {
if (i < 1 || i > L->length) { // 检查删除位置是否合法
return 0; // 删除失败
}
*e = L->data[i - 1]; // 保存被删除元素的值
// 将第i个位置之后的元素前移
for (int j = i; j < L->length; j++) {
L->data[j - 1] = L->data[j];
}
L->length--; // 表长减1
return 1; // 删除成功
}
// 8. 建立顺序表(通过数组创建)
void CreateList(SqList *L, ElemType a[], int n) {
if (n > MAXSIZE) {
printf("数组长度超过顺序表最大容量!\n");
return;
}
for (int i = 0; i < n; i++) {
L->data[i] = a[i];
}
L->length = n;
}
// 打印顺序表
void PrintList(SqList L) {
printf("顺序表内容:");
for (int i = 0; i < L.length; i++) {
printf("%d ", L.data[i]);
}
printf("\n");
}
int main() {
SqList L;
ElemType e;
// 测试各个操作
InitList(&L);
printf("初始化后,顺序表是否为空:%s\n", ListEmpty(L) ? "是" : "否");
ElemType arr[] = {1, 3, 5, 7, 9};
CreateList(&L, arr, 5);
PrintList(L);
printf("当前顺序表长度:%d\n", ListLength(L));
printf("元素5的位置:%d\n", LocateElem(L, 5));
ListInsert(&L, 3, 4);
printf("在第3个位置插入4后:");
PrintList(L);
ListDelete(&L, 4, &e);
printf("删除第4个位置的元素%d后:", e);
PrintList(L);
DestroyList(&L);
printf("销毁后顺序表长度:%d\n", ListLength(L));
return 0;
}
三、线性表的的链式存储结构----链表
单链表
线性表的的每个元素用一个内存结点存储每个内存结点存储,每个内存结点包含本身的数据域和表示元素之间逻辑关系用指针实现(也就是指针域)。
node :结点
箭头:指针
data: 每个结点存储的数据
单链表示意图
相关代码示例
#include <stdio.h>
#include <stdlib.h>
// 定义单链表节点结构
typedef struct Node {
int data;
struct Node* next;
} Node;
// 创建新节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 在链表头部插入节点
Node* insertAtBeginning(Node* head, int data) {
Node* newNode = createNode(data);
newNode->next = head;
return newNode;
}
// 在链表尾部插入节点
Node* insertAtEnd(Node* head, int data) {
Node* newNode = createNode(data);
if (head == NULL) {
return newNode;
}
Node* temp = head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
return head;
}
// 删除指定值的第一个节点
Node* deleteNode(Node* head, int key) {
Node* temp = head;
Node* prev;
if (temp != NULL && temp->data == key) {
head = temp->next;
free(temp);
return head;
}
while (temp != NULL && temp->data != key) {
prev = temp;
temp = temp->next;
}
if (temp == NULL) return head;
prev->next = temp->next;
free(temp);
return head;
}
// 查找指定值的节点
Node* searchNode(Node* head, int key) {
Node* temp = head;
while (temp != NULL) {
if (temp->data == key) {
return temp;
}
temp = temp->next;
}
return NULL;
}
// 遍历链表
void traverseList(Node* head) {
Node* temp = head;
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}
// 释放链表内存
void freeList(Node* head) {
Node* temp;
while (head != NULL) {
temp = head;
head = head->next;
free(temp);
}
}
int main() {
Node* head = NULL;
head = insertAtBeginning(head, 1);
head = insertAtEnd(head, 2);
head = insertAtEnd(head, 3);
printf("链表元素: ");
traverseList(head);
head = deleteNode(head, 2);
printf("删除元素 2 后链表元素: ");
traverseList(head);
Node* found = searchNode(head, 3);
if (found != NULL) {
printf("找到元素 3\n");
} else {
printf("未找到元素 3\n");
}
freeList(head);
return 0;
}
1. 关键操作说明
头插法:新结点始终插入头结点之后(逆序建表)。
尾插法:维护尾指针,新结点插入链表尾部(顺序建表)。
(根据题目要求选择合适的插入方法)
插入/删除:需先找到前驱结点(第i-1个结点)。
销毁链表:需要逐个释放所有结点内存。
2. 时间复杂度
按位查找/插入/删除:O(n)
头插法建表:O(n)
尾插法建表:O(n)
单链表虽然实现简单且内存利用率高,但也存在以下几个主要缺点:
(1)单向遍历,无法逆向访问
问题:单链表的指针域仅指向后继结点,无法直接访问前驱结点。
(2)查找效率低
问题:单链表只能顺序访问,不支持随机访问。
(3)删除/插入操作依赖前驱结点
问题:在位置 i
插入或删除结点时,需要先找到第 i-1
个结点(前驱结点)。
(4)内存空间额外开销
问题:每个结点需要额外存储指针域(next
指针)。
(5) 尾部操作效率低
问题:单链表需遍历到尾结点才能执行尾部插入/删除。
(6)无法直接实现某些高效算法
问题:单链表结构限制了算法的灵活性,快速排序等需随机访问的算法在单链表上难以高效实现。
解决单向链表的缺点----双线链表
单向链表每个节点只有一个next指针,增加一个pre指针用来指向前一个节点,这样就能够往前往后的双向查找了。
双向链表
插入操作
s->next=p->next; //将s结点插入p结点之后
p->next->prev=s;
s->prev=p;
p->next=s;
#include <stdio.h>
#include <stdlib.h>
// 定义双向链表节点结构
typedef struct Node {
int data;
struct Node* prev;
struct Node* next;
} Node;
// 创建新节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(1);
}
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
// 在链表头部插入节点
Node* insertAtBeginning(Node* head, int data) {
Node* newNode = createNode(data);
if (head == NULL) {
return newNode;
}
newNode->next = head;
head->prev = newNode;
return newNode;
}
// 在链表尾部插入节点
Node* insertAtEnd(Node* head, int data) {
Node* newNode = createNode(data);
if (head == NULL) {
return newNode;
}
Node* temp = head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
newNode->prev = temp;
return head;
}
// 删除指定值的第一个节点
Node* deleteNode(Node* head, int key) {
if (head == NULL) {
return head;
}
Node* temp = head;
if (temp->data == key) {
head = temp->next;
if (head != NULL) {
head->prev = NULL;
}
free(temp);
return head;
}
while (temp != NULL && temp->data != key) {
temp = temp->next;
}
if (temp == NULL) {
return head;
}
if (temp->prev != NULL) {
temp->prev->next = temp->next;
}
if (temp->next != NULL) {
temp->next->prev = temp->prev;
}
free(temp);
return head;
}
// 查找指定值的节点
Node* searchNode(Node* head, int key) {
Node* temp = head;
while (temp != NULL) {
if (temp->data == key) {
return temp;
}
temp = temp->next;
}
return NULL;
}
// 正向遍历链表
void traverseForward(Node* head) {
Node* temp = head;
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}
// 反向遍历链表
void traverseBackward(Node* head) {
if (head == NULL) {
return;
}
Node* temp = head;
while (temp->next != NULL) {
temp = temp->next;
}
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->prev;
}
printf("\n");
}
// 释放链表内存
void freeList(Node* head) {
Node* temp;
while (head != NULL) {
temp = head;
head = head->next;
free(temp);
}
}
int main() {
Node* head = NULL;
head = insertAtBeginning(head, 1);
head = insertAtEnd(head, 2);
head = insertAtEnd(head, 3);
printf("正向遍历链表元素: ");
traverseForward(head);
printf("反向遍历链表元素: ");
traverseBackward(head);
head = deleteNode(head, 2);
printf("删除元素 2 后正向遍历链表元素: ");
traverseForward(head);
Node* found = searchNode(head, 3);
if (found != NULL) {
printf("找到元素 3\n");
} else {
printf("未找到元素 3\n");
}
freeList(head);
return 0;
}
双向链表 vs 单链表对比
操作 | 双向链表 | 单链表 |
---|---|---|
插入/删除 | 可直接操作前驱和后继(O(1)) | 需遍历找前驱(O(n)) |
逆向遍历 | 直接通过prior 指针(O(n)) | 无法直接实现(需栈辅助) |
空间开销 | 每个结点多一个指针域 | 仅一个指针域 |
适用场景 | 需要频繁逆向操作或双向查找 | 简单线性操作 |
双向链表通过空间换时间,显著提升了插入、删除和遍历的灵活性!
双链表的优化----循环链表
#include <stdio.h>
#include <stdlib.h>
// 定义单向循环链表节点结构
typedef struct Node {
int data;
struct Node* next;
} Node;
// 创建新节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 初始化单向循环链表
Node* initCircularSingleList(int data) {
Node* head = createNode(data);
// 让头节点的 next 指针指向自身,形成循环
head->next = head;
return head;
}
// 遍历单向循环链表
void traverseCircularSingleList(Node* head) {
if (head == NULL) {
return;
}
Node* temp = head;
do {
printf("%d ", temp->data);
temp = temp->next;
} while (temp != head);
printf("\n");
}
int main() {
// 初始化单向循环链表,初始数据为 1
Node* head = initCircularSingleList(1);
printf("单向循环链表元素: ");
traverseCircularSingleList(head);
return 0;
}