数据结构——线性表(核心操作,附代码)

文章目录

一、顺序表的核心操作

顺序表是用一段地址连续的存储单元依次存储线性表中的元素,元素的逻辑顺序与物理存储顺序完全一致。

底层依赖数组实现。特点是能通过数组下标直接访问元素。

1.1定义及初始化

  • max_size表示顺序表的最大容量,即有有多少个元素。
  • length表示当前元素的个数,其范围为【0,max_size】
  • index表示数组的有效索引,其范围为【0,length-1】
cpp 复制代码
#define MAX_SIZE 100 
	struct SeqList {
	int data [MAX_SIZE]; 
	int length; 
};

简单操作包括:初始化链表,判空,判满,获取长度。

cpp 复制代码
// 初始化顺序表
void initList (SeqList& list) {
	list.length = 0; // 初始为空表
}
// 判断顺序表是否为空
bool isEmpty (const SeqList& list) {
	return list.length == 0;
}
// 判断顺序表是否已满
bool isFull (const SeqList& list) {
	return list.length == MAX_SIZE;
}
// 获取顺序表长度
int getLength (const SeqList& list) {
	return list.length;
}

1.2遍历

遍历即通过数组下标从 0 到 length-1 依次访问元素。

cpp 复制代码
// 打印顺序表元素
void printList (const SeqList& list) {
	if (isEmpty (list)) return ;
	for (int i = 0; i <= list.length-1; ++i) {
		cout << list.data [i] << " ";
	}
}

1.3查(按值&按索引)

按索引查找非常快,无需遍历,直接通过数组下标(索引)进行访问。注意索引范围。时间复杂度O(1)。

  • length的范围为【0,max_size】
  • 数组的有效索引的范围为【0,length-1】

按值查找需要遍历整个顺序表,逐个比较当前元素值与目标值,找到匹配项后,返回数组下标。时间复杂度O(n)。

cpp 复制代码
// 按索引查找元素
int getElement(const SeqList& list, int index) {
    if (index < 0 || index >= list.length)   throw out_of_range("索引越界");
    return list.data[index];  // 直接访问数组对应下标
}
// 按值查找元素,返回第一个匹配元素的索引,未找到返回 - 1
int findElement(const SeqList& list, int value) {
    for (int i = 0; i < list.length; ++i) {  // 遍历所有元素
        if (list.data[i] == value) {
            return i;  // 找到后返回索引
        }
    }
    return -1;  // 遍历结束未找到
}

1.4增(指定索引)

在指定索引 index 处插入一个新元素,后续元素依次后移。

cpp 复制代码
void insertElement(SeqList& list, int index, int value) {
    if (isFull(list)) throw runtime_error("表满");  // 检查容量
    if (index < 0 || index > list.length) throw out_of_range("位置无效");
    // 从最后一个元素开始,依次后移一位(避免覆盖)
    for (int i = list.length-1; i >= index; --i) {
        list.data[i+1] = list.data[i];
    }
    list.data[index] = value;  // 插入新元素
    list.length++;  // 长度+1
}

1.5删(指定索引)

删除指定索引 index 处的元素,后续元素依次前移。

cpp 复制代码
void deleteElement(SeqList& list, int index) {
    if (isEmpty(list)) throw runtime_error("表空");  // 检查空表
    if (index < 0 || index >= list.length) throw out_of_range("位置无效");
    // 从删除位置的下一个元素开始,依次前移一位(覆盖被删除元素)
    for (int i = index+1; i <= list.length-1; ++i) {
        list.data[i-1] = list.data[i];
    }
    list.length--;  // 长度-1
}

1.6改(指定索引)

将指定索引 index 处的元素值修改为 newValue。【直接覆盖】

cpp 复制代码
void updateElement(SeqList& list, int index, int newValue) {
    if (index < 0 || index >= list.length)  throw out_of_range("修改位置无效");
    list.data[index] = newValue;  // 直接覆盖旧值
}

二、链表的核心操作

2.1定义及初始化

  • ListNode结构体定义:值val和指向下一个节点的指针next
  • dummy:虚拟头结点,指向开始节点简化边界情况
  • dummy->next:开始节点,第一个存储有效数据的节点
  • length:记录链表长度
cpp 复制代码
// 链表节点结构体
struct ListNode {
    int val;
    ListNode* next;
    // 构造函数
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode* n) : val(x), next(n) {}
};
ListNode* dummy;  // 虚拟头节点(所有操作的统一起点)
int length;       // 链表长度
cpp 复制代码
// 初始化链表
LinkedList() {
    dummy = new ListNode(0);  // 虚拟头节点,值无实际意义
    length = 0;
}

2.2遍历

cpp 复制代码
void printList() {
    ListNode* h = dummy->next;//h指向开始节点
    if (h == nullptr) return ;
    while (h != nullptr) {
        cout << h->val << " -> ";
        h = h->next;
    }
}

2.3查(按索引&按值)

cpp 复制代码
// 按索引获取元素
int get(int index) {
    // 索引无效检查
    if (index < 0 || index >= length)  throw out_of_range("索引越界:无效的查询索引");
    ListNode* cur = dummy;//h指向头结点
    // 从头结点出发,移动index+1次到达目标节点
    for (int i = 0; i <= index; ++i) {
        cur = cur->next;
    }
    //执行完后,cur 指向的是 index 对应的节点
    return cur->val;
}
// 按值查找,返回第一个匹配元素的索引,未找到返回-1
int find(int value) {
    ListNode* cur = dummy->next;  // 从真实头节点开始遍历
    for (int i = 0; i < length; ++i) {
        if (cur->val == value)  return i; 
        cur = cur->next;
    }
    return -1; 
}

2.4增

插入的核心操作:

cpp 复制代码
// 在指定索引处插入元素
void addAtIndex(int index, int val) {
    // 索引无效检查(允许index=length,即尾部插入)
    if (index < 0 || index > length) throw out_of_range("插入位置无效");
    ListNode* cur = dummy;
    // 从dummy出发,移动到达插入位置的前一个节点index-1
    for (int i = 0; i <= index-1; ++i) {
        cur = cur->next;
    }
    // 创建新节点并插入
    ListNode* newNode = new ListNode(val);
    newNode->next = cur->next;  
    cur->next = newNode;        
    length++;
}
  • 头部插入等价于在索引 0 处插入
  • 尾部插入等价于在索引 length 处插入

2.5删(按索引)

删除的核心操作

cpp 复制代码
// 删除指定索引处的元素
void deleteAtIndex(int index) {
    if (index < 0 || index >= length)  throw out_of_range("删除位置无效");

    ListNode* cur = dummy;
    // 从dummy出发,移动到达删除位置的前一个节点
    for (int i = 0; i <= index-1; ++i) {
        cur = cur->next;
    }
    // 保存待删除节点,修改指针,释放内存
    ListNode* temp = cur->next;
    cur->next = cur->next->next;  // 跳过待删除节点
    delete temp;                  // 释放内存,避免泄漏
    length--;
}

2.6改

cpp 复制代码
// 修改指定索引处的元素值
void updateAtIndex(int index, int newValue) {
    if (index < 0 || index >= length)  throw out_of_range("修改位置无效");
    
    ListNode* cur = dummy;
    // 从dummy出发,移动到达目标节点index处
    for (int i = 0; i <= index; ++i) {
        cur = cur->next;
    }
    cur->val = newValue;  // 修改值
}

设计链表

https://leetcode.cn/problems/design-linked-list/?envType=problem-list-v2\&envId=linked-list