线性表是数据结构中最基础、最常用的数据结构之一,它在实际应用中非常广泛。无论是操作系统中的内存管理,还是数据库中的索引结构,线性表都扮演着重要角色。
线性表是由n
(n ≥ 0
)个数据元素组成的有限序列。它的逻辑结构具有以下特点:
每个元素在线性表中都有一个唯一的前驱(除了第一个元素)
每个元素在线性表中都有一个唯一的后继(除了最后一个元素)
元素之间是线性关系(即一对一的关系)
例如,一个学生的成绩列表 [90, 85, 78, 92, 88]
就是一个线性表。
线性表的抽象数据类型定义了它的一组操作接口,而不关心具体的实现细节。以下是线性表的主要操作:
初始化:创建一个空的线性表
插入:在指定位置插入一个新元素
删除:删除指定位置的元素
查找:查找某个元素的位置
遍历:依次访问线性表中的每个元素
清空:清空线性表中的所有元素
线性表中的元素可以是任意类型,例如整数、字符串、对象等。在实际应用中,线性表的元素类型通常根据具体需求定义。
顺序存储是用一段连续的内存空间来存储线性表的元素。通常用数组来实现顺序存储。顺序存储的特点:
优点:支持随机访问,访问速度快
缺点:插入和删除操作需要移动大量元素
插入操作需要将插入位置之后的所有元素向后移动一位,为新元素腾出空间。
初始状态:[a0, a1, a2, a3, a4]
插入位置:2,插入元素:x
操作后:[a0, a1, x, a2, a3, a4]
删除操作需要将删除位置之后的所有元素向前移动一位,填补空缺。
初始状态:[a0, a1, a2, a3, a4]
删除位置:2
操作后:[a0, a1, a3, a4]
#include
#include
#define MAXSIZE 100
typedef struct {
int data[MAXSIZE];
int length;
} SeqList;
// 初始化
void InitList(SeqList *L) {
L->length = 0;
}
// 插入
int Insert(SeqList *L, int pos, int value) {
if (pos < 1 || pos > L->length + 1 || L->length >= MAXSIZE) {
return 0;
}
int i; // 声明变量
for (i = L->length - 1; i >= pos - 1; i--) {
L->data[i + 1] = L->data[i];
}
L->data[pos - 1] = value;
L->length++;
return 1;
}
// 删除
int Delete(SeqList *L, int pos, int *value) {
if (pos < 1 || pos > L->length) {
return 0;
}
*value = L->data[pos - 1];
int i; // 声明变量
for (i = pos; i < L->length; i++) {
L->data[i - 1] = L->data[i];
}
L->length--;
return 1;
}
// 查找
int Locate(SeqList L, int value) {
int i; // 声明变量
for (i = 0; i < L.length; i++) {
if (L.data[i] == value) {
return i + 1;
}
}
return 0;
}
int main() {
SeqList L;
InitList(&L);
Insert(&L, 1, 10);
Insert(&L, 2, 20);
Insert(&L, 3, 30);
int value;
Delete(&L, 2, &value);
printf("Deleted value: %d\n", value);
int pos = Locate(L, 30);
printf("Position of 30: %d\n", pos);
return 0;
}
class SeqList:
def __init__(self, size=100):
self.data = [None] * size
self.length = 0
self.size = size
def insert(self, pos, value):
if pos < 1 or pos > self.length + 1 or self.length >= self.size:
return False
for i in range(self.length, pos - 1, -1):
self.data[i] = self.data[i - 1]
self.data[pos - 1] = value
self.length += 1
return True
def delete(self, pos):
if pos < 1 or pos > self.length:
return None
value = self.data[pos - 1]
for i in range(pos, self.length):
self.data[i - 1] = self.data[i]
self.length -= 1
return value
def locate(self, value):
for i in range(self.length):
if self.data[i] == value:
return i + 1
return 0
单链表是通过指针将线性表的元素链接起来的一种存储结构。每个节点包含两个部分:
数据域:存储数据元素
指针域:存储下一个节点的地址
单链表的特点:
优点:插入和删除操作不需要移动元素,只需修改指针
缺点:不支持随机访问,访问速度较慢
在单链表中插入一个新节点,需要修改前驱节点的指针。
初始状态:head -> a -> b -> c -> NULL
插入位置:a和b之间,插入x
操作后:head -> a -> x -> b -> c -> NULL
在单链表中删除一个节点,需要修改前驱节点的指针,跳过被删除的节点。
初始状态:head -> a -> b -> c -> NULL
删除位置:b
操作后:head -> a -> c -> NULL
#include
#include
typedef struct Node {
int data;
struct Node *next;
} Node, *LinkedList;
// 初始化
void InitList(LinkedList *L) {
*L = (LinkedList)malloc(sizeof(Node));
(*L)->next = NULL;
}
// 插入
void Insert(LinkedList L, int pos, int value) {
LinkedList p = L;
int i = 0;
while (p && i < pos - 1) {
p = p->next;
i++;
}
if (!p || i > pos - 1) return;
LinkedList newNode = (LinkedList)malloc(sizeof(Node));
newNode->data = value;
newNode->next = p->next;
p->next = newNode;
}
// 删除
int Delete(LinkedList L, int pos, int *value) {
LinkedList p = L;
int i = 0;
while (p->next && i < pos - 1) {
p = p->next;
i++;
}
if (!p->next || i > pos - 1) return 0;
LinkedList q = p->next;
*value = q->data;
p->next = q->next;
free(q);
return 1;
}
// 查找
LinkedList Locate(LinkedList L, int value) {
LinkedList p = L->next;
while (p) {
if (p->data == value)
return p;
p = p->next;
}
return NULL;
}
class Node:
def __init__(self, data=None):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = Node()
def insert(self, pos, value):
p = self.head
i = 0
while p and i < pos - 1:
p = p.next
i += 1
if not p or i > pos - 1:
return
new_node = Node(value)
new_node.next = p.next
p.next = new_node
def delete(self, pos):
p = self.head
i = 0
while p.next and i < pos - 1:
p = p.next
i += 1
if not p.next or i > pos - 1:
return None
q = p.next
p.next = q.next
return q.data
def locate(self, value):
p = self.head.next
while p:
if p.data == value:
return p
p = p.next
return None
循环链表是单链表的一种变体,其尾节点的指针域指向头节点,形成一个环。循环链表的特点:
没有明显的结束标志,需要通过遍历判断是否回到起点
适用于需要频繁从尾部插入或删除的场景
#include
#include
typedef struct Node {
int data;
struct Node *next;
} Node, *CirList;
// 初始化
void InitList(CirList *L) {
*L = (CirList)malloc(sizeof(Node));
(*L)->next = *L; // 指向自身形成循环
}
// 插入
void Insert(CirList L, int pos, int value) {
CirList p = L;
int i = 0;
while (p->next != L && i < pos - 1) {
p = p->next;
i++;
}
if (p->next == L && i < pos - 1) return;
CirList newNode = (CirList)malloc(sizeof(Node));
newNode->data = value;
newNode->next = p->next;
p->next = newNode;
}
// 删除
int Delete(CirList L, int pos, int *value) {
CirList p = L;
int i = 0;
while (p->next != L && i < pos - 1) {
p = p->next;
i++;
}
if (p->next == L || i > pos - 1) return 0;
CirList q = p->next;
*value = q->data;
p->next = q->next;
free(q);
return 1;
}
操作 | 顺序存储 | 单链表 | 循环链表 |
---|---|---|---|
插入 | O(n) | O(n) | O(n) |
删除 | O(n) | O(n) | O(n) |
查找 | O(1)(随机访问) | O(n) | O(n) |
空间性能 | 需预先分配固定大小空间 | 动态分配,无浪费 | 动态分配,无浪费 |
适用场景 | 频繁查找的场景 | 频繁插入/删除的场景 | 需要从尾部频繁操作的场景 |
顺序存储:适用于数据量较小且查找频繁的场景,例如数组实现的栈和队列。
单链表:适用于数据量较大且插入/删除频繁的场景,例如实现动态内存分配。
循环链表:适用于需要从尾部频繁操作的场景,例如循环队列。
线性表是数据结构的基础,理解它的逻辑结构和存储结构是学习其他复杂数据结构的前提。顺序存储和链式存储各有优缺点,选择合适的存储结构需要根据具体的应用场景权衡。通过本文的讲解和代码示例,相信读者已经对线性表有了全面的理解,可以灵活运用到实际开发中。