数据结构——线性表

发布于:2024-05-24 ⋅ 阅读:(63) ⋅ 点赞:(0)

目录

1. 线性表的定义和基本概念

2. 顺序表

2.1 顺序表的定义

2.2 顺序表的结构

2.3 顺序表的优缺点

2.4 顺序表的操作

2.5 顺序表的代码实现

代码说明:

2.6 演示过程

顺序表插入节点

顺序表删除节点

图示过程

3. 链表

3.1 链表的定义

3.2 链表的类型

单链表(Singly Linked List)

双向链表(Doubly Linked List)

循环链表(Circular Linked List)

3.3 链表的优缺点

优点:

缺点:

3.4 链表的操作

3.5 链表的代码实现

代码说明:

3.6 演示过程

链表插入节点

链表删除节点

4. 顺序表与链表的比较

实际应用场景举例


1. 线性表的定义和基本概念

  • 线性表(List)是数据结构中的一种基本结构,是一组有序的数据元素的集合。线性表中的数据元素有且仅有一个前驱和一个后继(第一个元素没有前驱,最后一个元素没有后继)。线性表是实现其他数据结构(如堆栈、队列)的基础。

2. 顺序表

2.1 顺序表的定义

  • 顺序表(Sequential List)是一种线性表,其存储结构为一块连续的内存空间。顺序表通过数组来实现,数组中的每个位置都存储线性表的一个元素。

2.2 顺序表的结构

顺序表一般包含以下几部分:

  • 存储数组:存储线性表的元素。
  • 最大容量:顺序表的最大存储容量。
  • 当前长度:顺序表中当前存储的元素个数。

2.3 顺序表的优缺点

优点

  • 随机访问性能好:可以通过下标快速访问任意位置的元素。
  • 查询效率高:在时间复杂度为O(1)的情况下访问元素。

缺点:

  • 插入和删除操作效率低:需要移动大量元素,时间复杂度为O(n)。
  • 内存浪费或溢出:数组大小固定,可能导致内存浪费或不足。

2.4 顺序表的操作

1、插入操作

  • 在顺序表中插入一个元素,需要将插入位置及其后的所有元素向后移动一个位置。

2、删除操作

  • 从顺序表中删除一个元素,需要将删除位置后的所有元素向前移动一个位置。

2.5 顺序表的代码实现

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int *array;
    int capacity;
    int length;
} SequentialList;

SequentialList* create_list(int capacity) {
    SequentialList* list = (SequentialList*)malloc(sizeof(SequentialList));
    list->array = (int*)malloc(capacity * sizeof(int));
    list->capacity = capacity;
    list->length = 0;
    return list;
}

void insert(SequentialList* list, int index, int value) {
    if (list->length >= list->capacity) {
        printf("List is full\n");
        return;
    }
    if (index < 0 || index > list->length) {
        printf("Index out of bounds\n");
        return;
    }

    for (int i = list->length; i > index; --i) {
        list->array[i] = list->array[i - 1];
    }
    list->array[index] = value;
    list->length++;
}

void delete(SequentialList* list, int index) {
    if (index < 0 || index >= list->length) {
        printf("Index out of bounds\n");
        return;
    }

    for (int i = index; i < list->length - 1; ++i) {
        list->array[i] = list->array[i + 1];
    }
    list->length--;
}

void print_list(SequentialList* list) {
    printf("[");
    for (int i = 0; i < list->length; ++i) {
        printf("%d", list->array[i]);
        if (i < list->length - 1) {
            printf(", ");
        }
    }
    printf("]\n");
}

void free_list(SequentialList* list) {
    free(list->array);
    free(list);
}

int main() {
    SequentialList* seq_list = create_list(10);
    insert(seq_list, 0, 1);
    insert(seq_list, 1, 2);
    insert(seq_list, 2, 3);
    print_list(seq_list);  // 输出: [1, 2, 3]
    delete(seq_list, 1);
    print_list(seq_list);  // 输出: [1, 3]
    free_list(seq_list);
    return 0;
}

代码说明:

  1. 创建顺序表
    • create_list 函数分配内存并初始化一个顺序表。
  2. 插入节点
    • insert 函数在指定索引位置插入一个值。如果顺序表已满或索引超出范围,打印错误信息。
  3. 删除节点
    • delete 函数删除指定索引位置的值。如果索引超出范围,打印错误信息。
  4. 打印顺序表
    • print_list 函数打印顺序表中的元素。
  5. 释放顺序表
    • free_list 函数释放分配的内存。

2.6 演示过程

顺序表插入节点

假设我们有一个顺序表,初始状态如下:

Index: 0   1   2
Data:  [A] [B] [C]

我们要在索引位置1插入一个新节点D。步骤如下:

  1. 从最后一个元素开始,依次向右移动元素,直到达到插入位置。
  2. 在指定位置插入新元素D。

插入后的顺序表如下:

Index: 0   1   2   3
Data:  [A] [D] [B] [C]

顺序表删除节点

接下来,我们删除索引位置2的节点B。步骤如下:

  1. 从删除位置的下一个元素开始,依次向左移动元素,覆盖删除位置的元素。
  2. 调整顺序表长度(在图示中表现为减少一个元素)。

删除后的顺序表如下:

Index: 0   1   2
Data:  [A] [D] [C]

图示过程

  1. 初始顺序表:

Index: 0   1   2
Data:  [A] [B] [C]

插入节点D的过程:

  • 将索引2的元素[C]向右移动到索引3
  • 将索引1的元素[B]向右移动到索引2
  • 在索引1处插入新元素[D]
Index: 0   1   2   3
Data:  [A] [D] [B] [C]

删除节点B的过程:

  • 将索引2的元素[C]向左移动到索引1
  • 删除原索引2的元素[B](在顺序表中体现为长度减少1)
Index: 0   1   2
Data:  [A] [D] [C]

3. 链表

3.1 链表的定义

  • 链表(Linked List)是一种线性表,其存储结构为一组任意存储单元。链表中的每个元素称为结点(Node),每个结点包含数据域和指针域。指针域指向下一个结点的地址。

3.2 链表的类型

单链表(Singly Linked List)
  • 每个结点只包含一个指向后继结点的指针。
双向链表(Doubly Linked List)
  • 每个结点包含两个指针,分别指向前驱结点和后继结点。
循环链表(Circular Linked List)
  • 链表的最后一个结点的指针指向第一个结点,形成一个环。

3.3 链表的优缺点

优点:
  • 插入和删除操作效率高:不需要移动其他元素,只需修改指针即可。
  • 动态内存分配:内存利用率高,不会出现内存浪费或不足的问题。
缺点:
  • 随机访问性能差:无法通过下标快速访问任意位置的元素,必须从头结点开始遍历。
  • 查询效率低:在最坏情况下,时间复杂度为O(n)。

3.4 链表的操作

插入操作

  • 在链表中插入一个元素,只需修改相关结点的指针。

删除操作

  • 从链表中删除一个元素,只需修改相关结点的指针。

3.5 链表的代码实现

#include <stdio.h>
#include <stdlib.h>

// 定义节点结构体
typedef struct Node {
    int value;
    struct Node* next;
} Node;

// 定义单链表结构体
typedef struct {
    Node* head;
} SinglyLinkedList;

// 创建节点
Node* create_node(int value) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    if (!new_node) {
        printf("Memory allocation error\n");
        exit(1);
    }
    new_node->value = value;
    new_node->next = NULL;
    return new_node;
}

// 初始化链表
SinglyLinkedList* create_list() {
    SinglyLinkedList* list = (SinglyLinkedList*)malloc(sizeof(SinglyLinkedList));
    if (!list) {
        printf("Memory allocation error\n");
        exit(1);
    }
    list->head = NULL;
    return list;
}

// 插入节点
void insert(SinglyLinkedList* list, int index, int value) {
    Node* new_node = create_node(value);
    if (index == 0) {
        new_node->next = list->head;
        list->head = new_node;
    } else {
        Node* prev = list->head;
        for (int i = 0; i < index - 1; ++i) {
            if (prev == NULL) {
                printf("Index out of bounds\n");
                return;
            }
            prev = prev->next;
        }
        new_node->next = prev->next;
        prev->next = new_node;
    }
}

// 删除节点
void delete(SinglyLinkedList* list, int index) {
    if (list->head == NULL) {
        printf("List is empty\n");
        return;
    }
    if (index == 0) {
        Node* temp = list->head;
        list->head = list->head->next;
        free(temp);
    } else {
        Node* prev = list->head;
        for (int i = 0; i < index - 1; ++i) {
            if (prev == NULL) {
                printf("Index out of bounds\n");
                return;
            }
            prev = prev->next;
        }
        if (prev->next == NULL) {
            printf("Index out of bounds\n");
            return;
        }
        Node* temp = prev->next;
        prev->next = temp->next;
        free(temp);
    }
}

// 打印链表
void print_list(SinglyLinkedList* list) {
    Node* current = list->head;
    printf("[");
    while (current != NULL) {
        printf("%d", current->value);
        if (current->next != NULL) {
            printf(", ");
        }
        current = current->next;
    }
    printf("]\n");
}

// 释放链表
void free_list(SinglyLinkedList* list) {
    Node* current = list->head;
    while (current != NULL) {
        Node* temp = current;
        current = current->next;
        free(temp);
    }
    free(list);
}

int main() {
    SinglyLinkedList* linked_list = create_list();
    insert(linked_list, 0, 1);
    insert(linked_list, 1, 2);
    insert(linked_list, 2, 3);
    print_list(linked_list);  // 输出: [1, 2, 3]
    delete(linked_list, 1);
    print_list(linked_list);  // 输出: [1, 3]
    free_list(linked_list);
    return 0;
}

代码说明:

创建节点

  • create_node 函数分配内存并初始化一个新节点。

初始化链表

  • create_list 函数分配内存并初始化一个空的单链表。

插入节点

  • insert 函数在指定索引位置插入一个值。如果索引超出范围,打印错误信息。

删除节点

  • delete 函数删除指定索引位置的值。如果索引超出范围或链表为空,打印错误信息。

打印链表

  • print_list 函数打印链表中的元素。

释放链表

  • free_list 函数释放分配的内存,防止内存泄漏。

3.6 演示过程

链表插入节点

假设我们有一个链表,初始状态如下:

head -> [A] -> [B] -> [C]

我们要在节点B和节点C之间插入一个新节点D。步骤如下:

  1. 创建新节点D。
  2. 将新节点D的指针指向节点C。
  3. 将节点B的指针指向新节点D。

插入后的链表如下:

head -> [A] -> [B] -> [D] -> [C]

链表删除节点

接下来,我们删除节点B。步骤如下:

  1. 将节点A的指针从节点B指向节点D。
  2. 释放节点B的内存(在实际编程中需要释放,但在图示中不需要表现出来)。

删除后的链表如下:

head -> [A] -> [D] -> [C]

初始链表:

+------+    +-----+    +-----+
| head | -> | [A] | -> | [B] | -> | [C] |
+------+    +-----+    +-----+

插入节点D的过程:

+------+    +-----+    +-----+    +-----+
| head | -> | [A] | -> | [B] | -> | [C] |
+------+    +-----+    +-----+
                     |
                     V
                  +-----+
                  | [D] |
                  +-----+

完成插入后的链表:

+------+    +-----+    +-----+    +-----+    +-----+
| head | -> | [A] | -> | [B] | -> | [D] | -> | [C] |
+------+    +-----+    +-----+    +-----+

删除节点B的过程:

+------+    +-----+    +-----+    +-----+    +-----+
| head | -> | [A] | -> | [B] | -> | [D] | -> | [C] |
+------+    +-----+    +-----+
                        |
                        V
                      (delete)

完成删除后的链表:

+------+    +-----+    +-----+    +-----+
| head | -> | [A] | -> | [D] | -> | [C] |
+------+    +-----+    +-----+

4. 顺序表与链表的比较

特性 顺序表 链表
存储结构 连续内存 不连续内存(节点)
访问时间复杂度 O(1) O(n)
插入时间复杂度 O(n) O(1)
删除时间复杂度 O(n) O(1)
空间利用率
动态调整大小 困难 容易
内存开销 高(指针开销)

实际应用场景举例

  1. 顺序表

    • 实现栈和队列:访问元素时间复杂度低。
    • 需要频繁访问但插入删除较少的场景,如数组处理和矩阵运算。
  2. 链表

    • 实现链式队列和链式栈:插入删除时间复杂度低。
    • 实现一些动态数据结构,如链表、哈希链表、图的邻接表表示。

希望这些能对刚学习算法的同学们提供些帮助哦!!!