数据结构———线性表

一.线性表的定义

   线性表:零个或多个元素的有限序列

   首先它是一个序列,其中的元素是有顺序的,即第一个元素无前驱,最后一个元素无后继,剩下的每个都有且只有一个前驱和后继,线性表的元素个数n叫表的长度,n=0时,线性表称作空表。其中每个元素的下标称作该元素在线性表中的位序
在较为复杂的线性表中,一个数据元素可以由若干个数据项组成。

二.线性表的抽象数据类型(ADT)

2.1.数据对象

D={ai|ai属于elemset,(i=1,2,...,n,n大于等于0)}

2.2.数据关系

R={<ai-1,ai>|ai-1,ai属于D}(序偶关系,一组有序的数)

2.3.基本操作

  • 初始化线性表

    • 操作: InitList()
    • 初始条件: 无
    • 操作结果: 构造一个空的线性表。
  • 销毁线性表

    • 操作: DestroyList()
    • 初始条件: 线性表已存在
    • 操作结果: 销毁线性表。
  • 清空线性表

    • 操作: ClearList()
    • 初始条件: 线性表已存在
    • 操作结果: 将线性表重置为空表。
  • 判断线性表是否为空

    • 操作: ListEmpty()
    • 初始条件: 线性表已存在
    • 操作结果: 若线性表为空,返回 true;否则返回 false
  • 获取线性表的长度

    • 操作: ListLength()
    • 初始条件: 线性表已存在
    • 操作结果: 返回线性表中元素的个数。
  • 获取指定位置的元素

    • 操作: GetElem(L,i.*e)
    • 初始条件: 线性表已存在,且 1 ≤ i ≤ 线性表长度
    • 操作结果: 返回线性表中第 i 个位置的元素。
  •  元素的位置

    • 操作: LocateElem(e)
    • 初始条件: 线性表已存在
    • 操作结果: 查找线性表中第一个值为 e 的元素,返回其位置;若不存在,返回 0。
  • 在指定位置插入元素

    • 操作: ListInsert(L,i, e)
    • 初始条件: 线性表已存在,且 1 ≤ i ≤ 线性表长度+1
    • 操作结果: 在线性表的第 i 个位置插入新元素 e,线性表长度加1。
  • 删除指定位置的元素

    • 操作: ListDelete(*L,i,*e)
    • 初始条件: 线性表已存在,且 1 ≤ i ≤ 线性表长度
    • 操作结果: 删除线性表的第 i 个位置的元素,线性表长度减1。
  • 遍历线性表

    • 操作: ListTraverse(L)
    • 初始条件: 线性表已存在
    • 操作结果: 从第一个元素到最后一个元素,依次对线性表中的每个数据元素做某种操作。

三.线性表的顺序存储结构

3.1.定义

线性表的顺序存储结构,指的是用一段地址连续(元素间不能有空)的存储单元依次存储线性表的数据元素(逻辑上相邻的元素在存储中也相邻)

3.2.方式

由于顺序表具有,地址连续,依次存放,随机存取,类型相同的特点。所以用一堆数组来存储线性表中的元素。
代码如下

#include <iostream>
using namespace std;

template <typename T>
class SeqList {
private:
    T* data;    // 存储数据的数组
    int maxSize; // 顺序表的最大容量
    int length;  // 当前顺序表的长度

我们可以发现顺序存储结构的三个属性

  • 存储空间的起始位置:数组data,他的存储位置就存储空间的存储位置
  • 线性表的最大存储容量:数组长度maxSize
  • 线性表当前的长度:length

注意:数组长度指的是存储空间的长度,一般是不变的(有编程手段可以实现动态数组,但会带来性能上的损耗),线性表长度则是看线性表中数据元素的个数,是可以变化的,就好比要装一堆衣服,先去拿了箱子,再把衣服一件件放进去,所以数组长度是要大于等于线性表长度的

3.3.地址计算方法

地址:存储器中的每个存储单元都有自己的编号,这个编号就叫地址
假设每个数据元素占用c个存储单元,loc(a1)为基地址,则有一个很简单的数学公式来计算地址
loc(ai)=loc(a1)+(i-1)*c
那么根据这个公式,我们可以观察出,对于计算机来说,在线性表中每个位置存入或取出数据所花的时间都是相等的,也就是一个常数,则这个操作的时间复杂度为O(1),一般把具有这种特点的结构叫随机存储结构 

3.4.具体操作

3.4.1.获取元素操作

int getElement(int index) {
        if (index < 0 || index >= length) {
            cout << "参数无效" << endl;
            return -1; // 返回一个无效值表示出错
        }
        return data[index];
    }
 

3.4.2.元素插入操作

void insertElement(int index, int element) {
        if (index < 0 || index > length) {
            cout << "参数无效" << endl;
            return;
        }
        if (length >= MAXSIZE) {
            cout << "线性表已满" << endl;
            return;
        }
        for (int i = length; i > index; --i) {
            data[i] = data[i - 1];
        }
        data[index] = element;
        length++;
    }
 思路

  1. 如果插入位置不合理,抛出异常
  2. 如果线性表长度大于等于数组长度,抛出异常或动态增加容量
  3. 从最后一个位置开始向前遍历到第i个位置,分别将他们向后移一个位置
  4. 把元素插入空位
  5. 表长加1

3.4.3元素删除操作

void deleteElement(int index) {
        if (index < 0 || index >= length) {
            cout << "参数无效" << endl;
            return;
        }
        for (int i = index; i < length - 1; ++i) {
            data[i] = data[i + 1];
        }
        length--;
    }
思路

  1. 删除位置不合理,抛出异常
  2. 取出删除元素
  3. 从删除元素开始向后遍历到最后一个元素的位置,分别将它们向前移一个位置
  4. 表长减1

我们可以观察出,获取元素的时间复杂度是0(1),而插入和删除最坏情况要遍历整个表,则时间复杂度是0(n)

3.4.4.完整代码
#include <iostream>
using namespace std;

const int MAXSIZE = 100;

class LinearList {
private:
    int data[MAXSIZE];
    int length;

public:
    // 构造函数
    LinearList() {
        length = 0;
    }

    // 创建线性表
    void createList(int arr[], int size) {
        if (size > MAXSIZE) {
            cout << "线性表创建成功" << endl;
            return;
        }
        for (int i = 0; i < size; ++i) {
            data[i] = arr[i];
        }
        length = size;
    }

    // 获取元素
    int getElement(int index) {
        if (index < 0 || index >= length) {
            cout << "参数无效" << endl;
            return -1; // 返回一个无效值表示出错
        }
        return data[index];
    }

    // 插入元素
    void insertElement(int index, int element) {
        if (index < 0 || index > length) {
            cout << "参数无效" << endl;
            return;
        }
        if (length >= MAXSIZE) {
            cout << "线性表已满" << endl;
            return;
        }
        for (int i = length; i > index; --i) {
            data[i] = data[i - 1];
        }
        data[index] = element;
        length++;
    }

    // 删除元素
    void deleteElement(int index) {
        if (index < 0 || index >= length) {
            cout << "参数无效" << endl;
            return;
        }
        for (int i = index; i < length - 1; ++i) {
            data[i] = data[i + 1];
        }
        length--;
    }

    // 显示线性表
    void displayList() {
        for (int i = 0; i < length; ++i) {
            cout << data[i] << " ";
        }
        cout << endl;
    }
};

int main() {
    LinearList list;
    int arr[] = { 1, 2, 3, 4, 5 };
    list.createList(arr, 5);

    cout << "表 ";
    list.displayList();

    cout << "2的元素为 " << list.getElement(2) << endl;

    list.insertElement(2, 10);
    cout << "把2插入10 ";
    list.displayList();

    list.deleteElement(3);
    cout << "删除3 ";
    list.displayList();

    

    cin.get();

    return 0;
}

3.5.线性表顺序结构的优缺点

    3.5.1.优点
  • 无须为表中元素之间的逻辑关系的增加而增加额外的存储空间
  • 可以快速的获取表中任意位置的元素  
    3.5.2.缺点
  • 插入和删除要移动大量元素
  • 长度变化较大时难以确定存储空间的容量
  • 造成存储空间“碎片”

四.线性表的链式存储结构

4.1.基础概念

我们思考顺序存储结构的缺点,发现顺序是因为数据元素间没有空隙,所以需要移动大量元素,十分耗费时间。
让元素间预留出一个空位如何,但是不能解决同一位置多次插入的问题
那提前分析并预留空位如何,这么搞效率太低,而且空间上损耗太大
现在要让元素间留有一定空位,干脆所有元素都不考虑相邻位置的问题,哪有空位就去哪,只需要在每个元素上做好标记,让我们可以通过标记找到它的下一个元素的位置(内存地址)。于是我们得到了链式存储结构,用任意的一组存储单元存储线性表的数据元素
而对于数据ai来说不同于顺序存储结构的是,它还需要存储一个指示其直接后继的信息
我们将存储元素信息的域称作数据域,把存储直接后继位置的域称作指针域(其中信息称作指针或链),这两部分组成数据元素的存储映像,称作结点(最后一个结点指向n个结点链结成一个链表就是线性表的链式存储结构
如果一个链表的每个结点只包含一个指针域,它就是单链表
单链表一般会在头一个结点前附设一个结点,称作头结点,头结点可以不存储任何信息,也可以存链表长度之类的

4.2.头指针和头结点的区别

4.2.1.头指针
  • 头指针是指向第一个结点的指针,如果链表有头结点,那头指针要指向头结点
  • 头指针有标志作用,所以一般用头指针冠以链表的名字
  • 头指针是链表的必要元素,不论链表是否为空
4.2.2头结点
  • 头结点是为了方便设立的,其内容一般无意义
  • 是链表的非必要元素

4.3单链表

4.3.1创建

 

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

// 定义节点结构
typedef struct Node {
    int data;                 // 数据域
    struct Node* next;        // 指针域,指向下一个节点
} Node;

4.3.2插入

一般有三种插入方法,分别为

  • 头部插入
  • 尾部插入
  • 指定位置插入

头部插入

void insertAtHead(int data) {
    // 创建新节点
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = head;   // 新节点的next指向当前头节点
    head = newNode;         // 更新头节点为新节点
}

尾部插入,要先遍历链表找到最后一个结点

void insertAtTail(int data) {
    // 创建新节点
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;  // 新节点的next为NULL

    if (head == NULL) {  // 如果链表为空
        head = newNode;  // 新节点为头节点
        return;
    }

    Node* current = head;
    while (current->next != NULL) {  // 找到最后一个节点
        current = current->next;
    }
    current->next = newNode;  // 将最后一个节点的next指向新节点
}

指定位置插入

void insertAtPosition(int data, int position) {
    // 创建新节点
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;

    if (position == 0) {  // 如果插入位置为0,相当于头部插入
        newNode->next = head;
        head = newNode;
        return;
    }

    Node* current = head;
    int count = 0;

    // 找到目标位置的前一个节点
    while (current != NULL && count < position - 1) {
        current = current->next;
        count++;
    }

    if (current == NULL) {  // 如果位置超出链表长度
        printf("Position out of bounds.\n");
        free(newNode);
        return;
    }

    newNode->next = current->next;  // 插入节点
    current->next = newNode;
}
4.3.3删除

  删除分为两种,按值删除和按位置删除

按值删除

void deleteByValue(int data) {
    if (head == NULL) {  // 如果链表为空
        printf("List is empty.\n");
        return;
    }

    if (head->data == data) {  // 如果头节点是要删除的节点
        Node* temp = head;
        head = head->next;
        free(temp);
        return;
    }

    Node* current = head;
    while (current->next != NULL && current->next->data != data) {
        current = current->next;
    }

    if (current->next == NULL) {  // 如果未找到数据
        printf("Value not found in the list.\n");
        return;
    }

    Node* temp = current->next;
    current->next = current->next->next;  // 删除节点
    free(temp);
}

按位置删除

void deleteByPosition(int position) {
    if (head == NULL) {  // 如果链表为空
        printf("List is empty.\n");
        return;
    }

    if (position == 0) {  // 如果要删除的是头节点
        Node* temp = head;
        head = head->next;
        free(temp);
        return;
    }

    Node* current = head;
    int count = 0;

    // 找到目标位置的前一个节点
    while (current != NULL && count < position - 1) {
        current = current->next;
        count++;
    }

    if (current == NULL || current->next == NULL) {  // 如果位置超出链表长度
        printf("Position out of bounds.\n");
        return;
    }

    Node* temp = current->next;
    current->next = current->next->next;  // 删除节点
    free(temp);
}

4.4双链表

在定义中加入一个指向前驱结点的指针

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

// 定义双链表节点
typedef struct Node {
    int data;
    struct Node* next;
    struct Node* prev;
} Node;

// 插入节点到双链表末尾
void insert(Node** head_ref, int new_data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    Node* last = *head_ref;
    new_node->data = new_data;
    new_node->next = NULL;

    // 如果链表为空,将新节点设为头节点
    if (*head_ref == NULL) {
        new_node->prev = NULL;
        *head_ref = new_node;
        return;
    }

    // 否则,找到链表的最后一个节点
    while (last->next != NULL) {
        last = last->next;
    }

    last->next = new_node;
    new_node->prev = last;
}

// 删除指定数据的节点
void deleteNode(Node** head_ref, int key) {
    Node* temp = *head_ref;

    // 找到需要删除的节点
    while (temp != NULL && temp->data != key) {
        temp = temp->next;
    }

    // 如果没有找到节点,直接返回
    if (temp == NULL) {
        return;
    }

    // 如果要删除的节点是头节点
    if (*head_ref == temp) {
        *head_ref = temp->next;
    }

    // 修改前驱节点的 next 指针
    if (temp->next != NULL) {
        temp->next->prev = temp->prev;
    }

    // 修改后继节点的 prev 指针
    if (temp->prev != NULL) {
        temp->prev->next = temp->next;
    }

    free(temp);
}

// 遍历双链表
void printList(Node* node) {
    Node* last;
    printf("Forward traversal: ");
    while (node != NULL) {
        printf("%d ", node->data);
        last = node;
        node = node->next;
    }
    printf("\n");
}

int main() {
    Node* head = NULL;

    insert(&head, 1);
    insert(&head, 2);
    insert(&head, 3);
    insert(&head, 4);

    printf("Original doubly linked list:\n");
    printList(head);

    deleteNode(&head, 3);

    printf("After deleting node with data 3:\n");
    printList(head);
    system("pause");
    return 0;
}

4.5循环链表

循环链表是一种特殊的链表形式,链表的最后一个节点的指针指向头节点,从而形成一个环。循环链表可以是单向的(单向循环链表)或双向的(双向循环链表)。

优点:

  • 从任意节点开始遍历,最终都会返回到该节点,适合需要循环访问的场景。
  • 更加节省空间,因为不需要额外的节点来指示链表的结尾。

缺点:

  • 可能会造成无限循环,需要额外的判断来避免死循环。
  • 操作相对普通链表更为复杂,需要注意指针操作。
#include <stdio.h>
#include <stdlib.h>

// 定义循环链表节点
typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 插入节点到循环链表
void insert(Node** head_ref, int new_data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    Node* last = *head_ref;
    new_node->data = new_data;
    new_node->next = *head_ref;

    if (*head_ref == NULL) {
        new_node->next = new_node;
        *head_ref = new_node;
        return;
    }

    while (last->next != *head_ref) {
        last = last->next;
    }

    last->next = new_node;
}

// 删除指定数据的节点
void deleteNode(Node** head_ref, int key) {
    if (*head_ref == NULL) {
        return;
    }

    Node *current = *head_ref, *prev;

    if (current->data == key && current->next == *head_ref) {
        free(current);
        *head_ref = NULL;
        return;
    }

    while (current->next != *head_ref && current->data != key) {
        prev = current;
        current = current->next;
    }

    if (current->data == key) {
        prev->next = current->next;
        free(current);
    }
}

// 遍历循环链表
void printList(Node* head) {
    Node* temp = head;
    if (head != NULL) {
        do {
            printf("%d ", temp->data);
            temp = temp->next;
        } while (temp != head);
    }
    printf("\n");
}

int main() {
    Node* head = NULL;

    insert(&head, 1);
    insert(&head, 2);
    insert(&head, 3);
    insert(&head, 4);

    printf("Original circular linked list:\n");
    printList(head);

    deleteNode(&head, 3);

    printf("After deleting node with data 3:\n");
    printList(head);

    return 0;
}

4.6静态链表

静态链表是用数组来实现的链表,虽然使用的是数组,但数组中的每个元素存储的内容类似于链表的节点。每个数组元素通常由数据部分和指针部分组成,其中指针部分存储的是下一个元素的索引,而不是指针。

优点:

  • 可以避免动态内存分配的开销,提高性能。
  • 在不支持动态内存分配的环境中使用。

缺点:

  • 大小固定,无法动态扩展。
  • 插入和删除操作复杂度较高,尤其是在数组大小较大时。
#include <stdio.h>
#define SIZE 100

// 静态链表节点定义
typedef struct {
    int data;
    int next;
} Node;

// 初始化静态链表
void initializeList(Node list[], int* head) {
    *head = -1;
    for (int i = 0; i < SIZE - 1; i++) {
        list[i].next = i + 1;
    }
    list[SIZE - 1].next = -1;
}

// 插入数据
void insert(Node list[], int* head, int* free, int data) {
    if (*free == -1) {
        printf("List is full!\n");
        return;
    }

    int new_index = *free;
    *free = list[*free].next;

    list[new_index].data = data;
    list[new_index].next = *head;
    *head = new_index;
}

// 删除数据
void deleteNode(Node list[], int* head, int* free, int key) {
    int prev = -1, current = *head;

    while (current != -1 && list[current].data != key) {
        prev = current;
        current = list[current].next;
    }

    if (current == -1) {
        printf("Node with data %d not found.\n", key);
        return;
    }

    if (prev == -1) {
        *head = list[current].next;
    } else {
        list[prev].next = list[current].next;
    }

    list[current].next = *free;
    *free = current;
}

// 遍历静态链表
void printList(Node list[], int head) {
    int current = head;
    while (current != -1) {
        printf("%d ", list[current].data);
        current = list[current].next;
    }
    printf("\n");
}

int main() {
    Node list[SIZE];
    int head, free;

    initializeList(list, &head);
    free = 0;

    insert(list, &head, &free, 1);
    insert(list, &head, &free, 2);
    insert(list, &head, &free, 3);

    printf("Original static linked list:\n");
    printList(list, head);

    deleteNode(list, &head, &free, 2);

    printf("After deleting node with data 2:\n");
    printList(list, head);

    return 0;
}

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值