首页 > 其他分享 >数据结构--线性表

数据结构--线性表

时间:2024-10-15 14:47:14浏览次数:3  
标签:线性表 -- 元素 next 链表 int 数据结构 节点 指针

一、线性表的类型定义

  1. 数据元素类型
    线性表由一系列数据元素组成,这些数据元素可以是基本数据类型(如整型、浮点型、字符型等),也可以是复杂的数据类型(如结构体、类、指针等)。

  2. 存储结构
    线性表的存储结构可以是顺序存储或链式存储。

    • 顺序存储:使用连续的存储空间来存储线性表的元素,通常通过数组来实现。
    • 链式存储:使用非连续的存储空间来存储线性表的元素,每个元素(称为节点)包含数据域和指针域(或链接域),指针域用于指向下一个节点。
  3. 基本操作
    线性表的基本操作包括初始化、插入、删除、查找、遍历等。

    • 初始化:创建一个空的线性表。
    • 插入:在指定位置插入一个新元素。
    • 删除:删除指定位置的元素。
    • 查找:查找指定元素的位置或值。
    • 遍历:按顺序访问线性表中的每个元素。
  4. 类型定义方式
    线性表的类型定义可以使用多种方式,如使用结构体(struct)在C语言中定义,或使用类(class)在C++、Java等面向对象语言中定义。

实例:

#include <stdio.h>  
#include <stdlib.h>  
  
#define MAXSIZE 100  // 定义线性表的最大长度  
  
typedef struct {  
    int data[MAXSIZE];  // 存储线性表元素的数组  
    int length;         // 线性表的当前长度  
} SqList;  
  
// 初始化  
void InitList(SqList *L) {  
    L->length = 0;  
}  
  
// 存取  
int GetElem(SqList L, int i, int *e) {  
    if (i < 1 || i > L.length) return 0;  // 非法位置  
    *e = L.data[i - 1];  
    return 1;  
}  
  
// 插入  
int ListInsert(SqList *L, int i, int e) {  
    if (i < 1 || i > L->length + 1) return 0;  // 非法位置  
    if (L->length == MAXSIZE) return 0;  // 表满  
    for (int k = L->length - 1; k >= i - 1; k--) {  
        L->data[k + 1] = L->data[k];  
    }  
    L->data[i - 1] = e;  
    L->length++;  
    return 1;  
}  
  
// 删除  
int ListDelete(SqList *L, int i, int *e) {  
    if (i < 1 || i > L->length) return 0;  // 非法位置  
    *e = L->data[i - 1];  
    for (int k = i; k < L->length; k++) {  
        L->data[k - 1] = L->data[k];  
    }  
    L->length--;  
    return 1;  
}  
  
// 检索(查找)  
int LocateElem(SqList L, int e) {  
    for (int i = 0; i < L.length; i++) {  
        if (L.data[i] == e) return i + 1;  
    }  
    return 0;  
}  
  
// 分解和排序(略)  
  
int main() {  
    SqList L;  
    InitList(&L);  
    // 插入元素  
    ListInsert(&L, 1, 10);  
    ListInsert(&L, 2, 20);  
    ListInsert(&L, 3, 30);  
    // 存取元素  
    int e;  
    GetElem(L, 2, &e);  
    printf("Element at position 2: %d\n", e);  
    // 删除元素  
    ListDelete(&L, 2, &e);  
    printf("Deleted element: %d\n", e);  
    // 检索元素  
    int pos = LocateElem(L, 30);  
    if (pos) {  
        printf("Element 30 found at position: %d\n", pos);  
    } else {  
        printf("Element 30 not found.\n");  
    }  
    return 0;  
}
class ListNode {  
    int data;  
    ListNode next;  
  
    ListNode(int data) {  
        this.data = data;  
        this.next = null;  
    }  
}  
  
class LinkedList {  
    ListNode head;  
    int length;  
  
    // 初始化  
    public LinkedList() {  
        this.head = null;  
        this.length = 0;  
    }  
  
    // 存取(这里只展示查找并打印第一个元素,存取特定位置元素需要遍历)  
    public void printFirstElem() {  
        if (head != null) {  
            System.out.println("First element: " + head.data);  
        } else {  
            System.out.println("List is empty.");  
        }  
    }  
  
    // 插入(在链表头部插入)  
    public void insert(int data) {  
        ListNode newNode = new ListNode(data);  
        newNode.next = head;  
        head = newNode;  
        length++;  
    }  
  
    // 删除(删除第一个匹配的元素)  
    public boolean delete(int data) {  
        if (head == null) return false;  
        if (head.data == data) {  
            head = head.next;  
            length--;  
            return true;  
        }  
        ListNode current = head;  
        while (current.next != null && current.next.data != data) {  
            current = current.next;  
        }  
        if (current.next != null) {  
            current.next = current.next.next;  
            length--;  
            return true;  
        }  
        return false;  
    }  
  
    // 检索(查找元素并返回其位置,这里简化为返回是否存在)  
    public boolean contains(int data) {  
        ListNode current = head;  
        while (current != null) {  
            if (current.data == data) return true;  
            current = current.next;  
        }  
        return false;  
    }  
  
    // 分解和排序(略)  
  
    public static void main(String[] args) {  
        LinkedList list = new LinkedList();  
        // 插入元素  
        list.insert(10);  
        list.insert(20);  
        list.insert(30);  
        // 存取元素(打印第一个元素)  
        list.printFirstElem();  
        // 删除元素  
        list.delete(20);  
        // 尝试存取元素(打印第一个元素)  
        list.printFirstElem();  
        // 检索元素  
        if (list.contains(30)) {  
            System.out.println("Element 30 found.");  
        } else {  
            System.out.println("Element 30 not found.");  
        }  
    }  
}

二、线性表的顺序表示和实现

     2.1线性表的顺序表示

        线性表的顺序存储,又称为顺序表,是一种使用连续存储单元来存储线性表数据元素的方式。在这种存储方式中,数据元素之间的逻辑关系和物理关系是一致的,即它们在内存中的存储顺序与它们在线性表中的顺序相同。

2.1.1 特点

  1. 元素连续存储:顺序表的元素在内存中占据一块连续的存储空间,这使得通过下标访问元素变得非常高效,时间复杂度为O(1)。

  2. 随机访问:由于元素是连续存储的,因此可以像访问数组一样,通过下标直接访问顺序表中的任意元素。

  3. 存储密度高:顺序表不需要为元素之间的逻辑关系而增加额外的存储空间,因此存储密度较高。

  4. 插入和删除操作可能低效:在顺序表中插入或删除元素时,可能需要移动大量的元素,以保持元素的连续性。特别是在表的中间或靠近末尾的位置进行插入或删除操作时,效率较低。

2.1.2基本操作

  1. 初始化:为顺序表分配一块足够大的连续存储空间,并设置表的初始状态(如长度为0)。

  2. 插入:在顺序表的指定位置插入一个新的元素。如果插入位置合法且表未满,则将该位置及其后的元素依次后移一位,然后将新元素插入到指定位置。

  3. 删除:从顺序表中删除指定位置的元素。如果删除位置合法且表不为空,则将该位置后的元素依次前移一位,然后更新表的长度。

  4. 查找:根据给定的元素值,在顺序表中查找该元素的位置。如果找到,则返回其位置(下标);否则,返回一个特殊值(如-1)表示未找到。

  5. 更新:根据给定的位置和元素值,更新顺序表中指定位置的元素。

  6. 遍历:依次访问顺序表中的每个元素,并对它们进行某种处理(如打印、计算等)。

2.1.3 实现

顺序表通常使用数组来实现。在C语言中,可以使用静态数组或动态数组(如通过mallocrealloc函数分配的数组)来存储顺序表的元素。在C++中,可以使用std::vector等容器类来实现顺序表。

2.1.4 注意事项

  1. 预分配空间:在使用顺序表之前,通常需要预分配一块足够大的连续存储空间。如果实际存储的元素数量超过了预分配的空间大小,就会发生存储溢出错误。

  2. 动态扩展:为了处理存储溢出问题,可以使用动态扩展技术。当顺序表的空间不足时,可以重新分配一块更大的连续存储空间,并将原有的元素复制到新的存储空间中。然而,这种操作可能会导致性能下降和内存碎片问题。

  3. 边界检查:在进行插入、删除和查找等操作时,需要进行边界检查以确保操作的合法性。例如,插入位置应该在表的长度加1的范围内;删除位置应该在表的长度范围内;查找的元素值应该在表的元素范围内等。

2.2 顺序表中基本操作的实现

2.2.1  顺序表初始化

        初始化顺序表通常涉及为数据元素分配一块连续的存储空间,并设置表的初始状态(如长度和容量)。

#define MAX_SIZE 100 // 顺序表的最大容量 


typedef struct { 
ElemType data[MAX_SIZE]; // 存储元素的数组 
int length; // 顺序表的当前长度 
} SqList; 


// 初始化顺序表 
void InitList(SqList *L) { 
L->length = 0; // 设置顺序表长度为0 
}

2.2.2  顺序表取值

取值操作通过给定的位置(下标)来获取顺序表中的元素。

// 获取顺序表中第i个位置的元素(1 ≤ i ≤ L->length) 
ElemType GetElem(SqList L, int i) { 
if (i < 1 || i > L.length) { 
// 下标不合法,可以进行错误处理,如返回特殊值或抛出异常 
return ERROR_VALUE; 
} 
return L.data[i - 1]; // 数组下标从0开始,因此需要减1 
}

2.2.3  顺序表的按值查找

按值查找操作通过给定的元素值来查找顺序表中该元素的位置。

// 在顺序表中查找值为e的元素,返回其位置(1 ≤ 位置 ≤ L->length),若不存在则返回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; // 未找到 
}

2.2.4  顺序表插入元素

插入元素操作在顺序表的指定位置插入一个新的元素,并可能需要移动元素以保持顺序表的连续性。

// 在顺序表的第i个位置插入元素e(1 ≤ i ≤ L->length + 1) 
int ListInsert(SqList *L, int i, ElemType e) { 
if (i < 1 || i > L->length + 1 || L->length == MAX_SIZE) { 
// 插入位置不合法或顺序表已满,可以进行错误处理,如返回错误码 
return ERROR_CODE; 
} 
for (int j = L->length; j >= i; j--) { 
L->data[j] = L->data[j - 1]; // 元素后移 
} 
L->data[i - 1] = e; // 插入新元素 
L->length++; // 顺序表长度加1 
return OK_CODE; // 插入成功 
}

2.2.5  顺序表删除元素

删除元素操作从顺序表的指定位置删除一个元素,并可能需要移动元素以保持顺序表的连续性。

// 删除顺序表的第i个位置的元素(1 ≤ i ≤ L->length) 
int ListDelete(SqList *L, int i, ElemType *e) { 
if (i < 1 || i > L->length) { 
// 删除位置不合法,可以进行错误处理,如返回错误码 
return ERROR_CODE; 
} 
*e = L->data[i - 1]; // 保存被删除的元素 
for (int j = i; j < L->length; j++) { 
L->data[j - 1] = L->data[j]; // 元素前移 
} 
L->length--; // 顺序表长度减1 
return OK_CODE; // 删除成功 
}

三、线性表的链式表示和实现

线性表的链式表示是一种常见的数据结构表示方式,它使用指针将一组数据元素按照其逻辑顺序连接起来。以下是对线性表链式表示的详细解释和实现:

3.1 链式表示的基本概念

  1. 结点:链式表示的基本单位是结点(Node),每个结点通常包含两部分信息:数据域(存放数据元素)和指针域(指向下一个结点)。
  2. 链表:n个结点链接成一个链表,即为线性表的链式存储结构。链表的起始结点称为头结点,尾结点的指针域为空(或指向一个特殊的结束标记)。

3.2 链表的类型

  1. 单向链表(Singly Linked List)

    • 每个节点包含数据元素和一个指向下一个节点的引用(指针)。
    • 只能从头节点开始顺序遍历到尾节点。
  2. 双向链表(Doubly Linked List)

    • 每个节点包含数据元素、一个指向下一个节点的引用和一个指向前一个节点的引用。
    • 可以在头尾两个方向上进行遍历。
  3. 带头节点的链表

    • 在链表的首部增加一个额外的头节点,该头节点通常不存储实际数据,仅作为链表的起始点。
    • 便于在链表的首部进行插入和删除操作,无需特殊处理头节点为空的情况。
  4. 不带头节点的链表

    • 链表的首部即为第一个存储数据的节点。
    • 在进行插入和删除操作时,需要特别注意头节点为空的情况。
  5. 循环链表(Circular Linked List)

    • 链表的最后一个节点指向头节点,形成一个环状结构。
    • 可以无限遍历链表,因为没有明确的终止点。
  6. 非循环链表

    • 链表的最后一个节点指向空(NULL),表示链表的结束。
    • 遍历链表时,当遇到指向空的指针时,表示已经到达链表的末尾。
  7. 静态链表

    • 采用数组方式实现的链表结构,通过数组的下标来模拟链表的指针。
    • 并非真正的链表,但在某些情况下可以替代链表进行使用。
  8. 跳表(Skip List)

    • 一种随机化的数据结构,允许快速查询、插入、删除和更新操作。
    • 通过多级索引来实现快速访问,类似于二分查找树,但实现更为简单。

3.3 单链表的实现

结点的定义

在C语言中,可以使用结构体(struct)来定义单链表的结点:

其中,ElemType是数据元素的类型,可以是整型(int)、浮点型(float)等。SingleLinkNode是结构体类型,SingleLinkList是指向结构体类型的指针类型。

typedef struct SingleLinkNode { 
ElemType data; // 存放数据 
struct SingleLinkNode *next; // 存放指向下一个元素结点的指针 
} SingleLinkNode, *SingleLinkList;

单链表的基本操作

(1)初始化

SingleLinkList_Init(SingleLinkList &SL) { 
SL = new SingleLinkNode; // 生成新结点作为头结点,用头指针SL指向头结点 
SL->next = NULL; // 头结点的指针域置空 
}

(2)获取当前元素个数

int SingleLinkList_Length(SingleLinkList &SL) { 
int count = 0; // 定义count计数 
SingleLinkList p; 
p = SL->next; // 定义p指向头结点后的第一个结点 
while (p->next != NULL) { // 当指针的下一个地址不为空时,count+1 
count++; 
p = p->next; // p指向p的下一个地址 
} 
return count; // 返回count的数据 
}

(3)插入数据元素

int SingleLinkList_Insert(SingleLinkList &SL, int i, ElemType e) { 
SingleLinkNode *s, *p; 
int j = 0; 
p = SL; 
while (p && j < i - 1) { // 寻找第i-1个结点 
p = p->next; 
++j; 
} 
if (!p || j > i - 1) { // i大于表长或者小于1 
return 0; 
} 
s = new SingleLinkNode; // 生成新结点 
s->data = e; // 将结点s的数据域置为e 
s->next = p->next; // 新的结点的指针指向i结点 
p->next = s; // 修改p的指针域指向新的结点 
return 1; 
}

(4)删除数据元素

int SingleLinkList_Delete(SingleLinkList &SL, int i) { 
SingleLinkList p, q; 
int j = 0; 
p = SL; 
while ((p->next) && (j < i - 1)) { // 查找第i-1个结点,p指向该结点 
p = p->next; 
++j; 
} 
if (!(p->next) || (j > i - 1)) { // 当i>n或i<1时,删除位置不合理 
return 0; 
} 
q = p->next; // 临时保存被删结点的地址以备释放 
p->next = q->next; // 改变删除结点前驱结点的指针域 
delete q; // 释放删除结点的空间 
return 1; 
}

(5)取数据元素

ElemType SingleLinkGetElem(SingleLinkList &SL, int i, ElemType &e) { 
SingleLinkNode *p; 
int j; 
p = SL->next; // 初始化,p指向首元结点 
j = 1; // 计数器j初值赋为1 
while (p && j < i) // 顺链域向后扫描,直到p为空或p指向第i个元素 
{ 
p = p->next; // p指向下一个结点 
++j; // 计数器j相应加1 
} 
if (!p || j > i) { // i值不合法(i > n 或 i <= 0) 
return NULL; // 或其他表示错误的值/方式 
} 
e = p->data; // 取第i个结点的数据域 
return e; 
}

(6)销毁单链表

因为单链表的结点空间是程序动态申请的,而系统只负责自动回收程序中静态分配的内存空间,所以和顺序表相比,单链表需要增加一个销毁单链表的操作,用来在调用程序退出前释放动态申请的内存空间。

3.4 循环链表的定义与实现

循环链表是链式存储结构的一种特殊形式,其特点是表中最后一个节点的指针域指向头节点,从而使整个链表形成一个环状结构。这种结构使得链表中的元素可以无限循环地被访问,为某些特定场景下的操作提供了便利。

循环链表的实现

循环链表的实现通常涉及以下几个关键步骤和要点:

  1. 节点定义

    • 循环链表的节点通常包含数据域和指针域。数据域用于存储节点的数据,而指针域则用于指向下一个节点。
    • 在C语言中,可以通过结构体(struct)来定义循环链表的节点。
  2. 链表初始化

    • 初始化循环链表时,需要创建一个头节点,并将头节点的指针域指向自己,表示一个空链表。
    • 也可以根据需要初始化链表中的其他节点,并将它们链接起来形成环状结构。
  3. 插入操作

    • 在循环链表中插入节点时,需要找到插入位置的前一个节点,并修改其指针域以指向新节点。
    • 新节点的指针域需要指向插入位置后的节点(在单循环链表中)或前一个节点(在双向循环链表中,如果考虑前驱指针的话)。
    • 特别注意,当在链表头部插入新节点时,需要更新尾节点的指针域,使其指向新节点(如果链表之前为空,则尾节点也指向新节点)。
  4. 删除操作

    • 删除循环链表中的节点时,需要找到要删除的节点的前一个节点,并修改其指针域以跳过要删除的节点。
    • 如果要删除的节点是头节点,则需要特别处理,更新尾节点的指针域和头节点的指针域。
    • 释放要删除的节点的内存空间。
  5. 遍历操作

    • 遍历循环链表时,可以从头节点开始,依次访问每个节点,直到再次回到头节点。
    • 由于循环链表是环状的,因此需要设置适当的停止条件来防止进入无限循环。常见的停止条件包括遍历指定次数、访问到某个特定节点或满足某个条件等。
  6. 其他操作

    • 根据需要,循环链表还可以实现其他操作,如查找节点、更新节点数据、计算链表长度等。
    • 这些操作通常基于插入、删除和遍历等基本操作来实现。

示例代码(C语言)

以下是一个简单的单循环链表的示例代码,包括节点定义、链表初始化、插入、删除和遍历等操作:

#include <stdio.h>  
#include <stdlib.h>  
  
// 定义链表节点结构体  
typedef struct Node {  
    int data; // 节点的数据  
    struct Node* next; // 指向下一个节点的指针  
} Node;  
  
// 初始化链表  
Node* initList() {  
    Node* head = (Node*)malloc(sizeof(Node)); // 创建头节点  
    head->data = 0; // 头节点的数据设为0(或任意值,通常不存储实际数据)  
    head->next = head; // 头节点的指针指向自己,表示空链表  
    return head; // 返回头节点  
}  
  
// 插入节点(在尾部插入)  
void insertAtTail(Node* head, int value) {  
    Node* newNode = (Node*)malloc(sizeof(Node)); // 创建新节点  
    newNode->data = value; // 新节点的数据设为要插入的值  
    Node* temp = head;  
    while (temp->next != head) { // 找到最后一个节点  
        temp = temp->next;  
    }  
    temp->next = newNode; // 最后一个节点的指针指向新节点  
    newNode->next = head; // 新节点的指针指向头节点,完成插入  
}  
  
// 删除节点(按值删除)  
void deleteNode(Node* head, int value) {  
    Node* temp = head;  
    Node* prev = NULL;  
    while (temp->next != head) { // 遍历链表  
        if (temp->data == value) { // 找到要删除的节点  
            if (prev == NULL) { // 如果要删除的节点是头节点(虽然这里按值删除不会直接判断头节点,但为完整性考虑)  
                // 需要特别处理,更新尾节点的指针域和头节点的指针域(这里简化处理,只考虑非头节点情况)  
                // 实际上,在单循环链表中直接按值删除头节点是复杂的,因为需要知道尾节点  
                // 一种方法是先遍历找到尾节点,再处理头节点;另一种方法是维护一个额外的头节点指针或尾节点指针  
                // 这里为了简化,我们假设不会删除头节点(或将其视为特殊情况处理)  
                printf("Cannot delete head node directly in this simple implementation.\n");  
                return;  
            }  
            prev->next = temp->next; // 前一个节点的指针指向要删除节点的下一个节点  
            free(temp); // 释放要删除的节点的内存空间  
            return; // 删除成功,退出函数  
        }  
        prev = temp;  
        temp = temp->next;  
    }  
    printf("Node with value %d not found.\n", value); // 要删除的节点不存在  
}  
  
// 遍历链表  
void traverseList(Node* head) {  
    Node* temp = head->next; // 从头节点的下一个节点开始遍历(跳过头节点,因为它不存储实际数据)  
    while (temp != head) { // 遍历直到再次回到头节点  
        printf("%d ", temp->data); // 输出节点的数据  
        temp = temp->next; // 移动到下一个节点  
    }  
    printf("\n"); // 输出换行符以区分不同的遍历结果  
}  
  
int main() {  
    Node* list = initList(); // 初始化链表  
    insertAtTail(list, 1); // 在尾部插入节点1  
    insertAtTail(list, 2); // 在尾部插入节点2  
    insertAtTail(list, 3); // 在尾部插入节点3  
    traverseList(list); // 遍历链表并输出节点的数据:1 2 3   
    deleteNode(list, 2); // 删除值为2的节点  
    traverseList(list); // 遍历链表并输出节点的数据:1 3   
    // 注意:这里没有处理头节点的删除情况,因为在这个简单实现中我们假设不会删除头节点  
    // 如果需要删除头节点,需要额外的逻辑来处理尾节点的指针域和更新头节点指针  
    return 0;  
}

3.5 双链表的定义与实现

双链表(Doubly Linked List)是由一系列节点(Node)组成的链式数据结构。每个节点包含三个部分:数据域(Data)、前驱指针域(Prior Pointer)和后继指针域(Next Pointer)。

  1. 数据域:用于存储节点的数据。
  2. 前驱指针域:用于存储指向前一个节点的指针。
  3. 后继指针域:用于存储指向后一个节点的指针。

由于双链表节点具有前驱和后继两个指针,因此可以实现双向遍历,即可以从头节点遍历到尾节点,也可以从尾节点遍历到头节点。

双链表的实现

双链表的实现通常涉及以下几个基本操作:创建和初始化链表、插入节点、删除节点、查找节点和遍历链表。

  • 创建和初始化链表

在创建双链表时,首先需要定义一个结构体来表示链表的节点。然后,通过动态内存分配创建一个头节点,并初始化其前驱和后继指针为NULL(或在带头双向循环链表中,将头节点的前驱和后继指针都指向头节点本身,形成循环)。

// 定义双链表节点结构体  
struct DNode {  
    int data;           // 数据域  
    struct DNode* prior; // 前驱指针域  
    struct DNode* next;  // 后继指针域  
};  
  
// 创建双链表(带头节点)  
struct DNode* createList() {  
    struct DNode* head = (struct DNode*)malloc(sizeof(struct DNode));  
    head->prior = head;  
    head->next = head;  
    return head;  
}
  • 插入节点

在双链表中插入节点时,需要指定插入位置,并修改插入位置前后节点的指针。插入操作可以分为头插、尾插和在指定位置插入。

  • 头插:在头节点之后插入新节点,将新节点的后继指针指向头节点的后继节点,将头节点的后继节点的前驱指针指向新节点,然后将头节点的后继指针指向新节点,最后将新节点的前驱指针指向头节点。
  • 尾插:在尾节点之前插入新节点,将新节点的前驱指针指向尾节点的前驱节点,将尾节点的前驱节点的后继指针指向新节点,然后将尾节点的前驱指针指向新节点,最后将新节点的后继指针指向尾节点。
  • 在指定位置插入:先找到指定位置的前一个节点,然后修改前一个节点和新节点的指针,完成插入操作。
// 头插节点  
void insertNodeAtHead(struct DNode* head, int data) {  
    struct DNode* newNode = (struct DNode*)malloc(sizeof(struct DNode));  
    newNode->data = data;  
    newNode->next = head->next;  
    newNode->prior = head;  
    head->next->prior = newNode;  
    head->next = newNode;  
}  
  
// 尾插节点(省略了部分代码,与头插类似,但操作相反)  
// ...  
  
// 在指定位置插入节点(省略了部分代码,需要找到指定位置的前一个节点)  
// ...
  • 删除节点

在双链表中删除节点时,需要找到要删除的节点,并修改其前后节点的指针。然后释放要删除节点的内存。

// 删除指定节点  
void deleteNode(struct DNode* p) {  
    if (p == NULL || p->prior == NULL || p->next == NULL) return;  
    p->prior->next = p->next;  
    p->next->prior = p->prior;  
    free(p);  
}
  • 查找节点

在双链表中查找节点时,可以采用遍历的方法。从头节点开始,依次比较每个节点的数据域,直到找到目标节点或遍历完整个链表。

// 查找节点  
struct DNode* findNode(struct DNode* head, int data) {  
    struct DNode* p = head->next;  
    while (p != head) {  
        if (p->data == data) return p;  
        p = p->next;  
    }  
    return NULL;  
}
  • 遍历链表

遍历双链表时,可以从头节点开始,依次访问每个节点,直到回到头节点(在循环链表中)或遍历完整个链表(在非循环链表中)。

// 遍历链表并打印节点数据  
void traverseList(struct DNode* head) {  
    struct DNode* p = head->next;  
    while (p != head) {  
        printf("%d ", p->data);  
        p = p->next;  
    }  
    printf("\n");  
}

四、顺序表和链表的比较 

特性/数据结构顺序表(Sequential List)链表(Linked List)
存储方式使用连续的存储单元使用不连续的存储单元,通过指针连接
访问速度支持快速随机访问,时间复杂度为O(1)不支持快速随机访问,需要从头节点开始遍历,时间复杂度为O(n)(n为节点数)
插入操作在指定位置插入元素需要移动元素,平均时间复杂度为O(n)在指定位置插入元素只需修改指针,时间复杂度为O(1)(但需找到插入位置,若从头开始查找则平均时间复杂度为O(n))但无需移动数据
删除操作删除指定位置的元素需要移动元素,平均时间复杂度为O(n)删除指定位置的元素只需修改指针,时间复杂度为O(1)(但需找到删除位置,若从头开始查找则平均时间复杂度为O(n))但无需移动数据
内存利用率内存利用率高,因为存储单元是连续的内存利用率相对较低,因为每个节点都需要额外的指针存储空间,且可能存在内存碎片
空间分配需要预先分配固定大小的数组动态分配内存,可以根据需要增加或减少节点
适用场景适用于需要频繁访问元素的场景适用于需要频繁插入和删除元素的场景
稳定性插入和删除操作可能导致大量元素移动,稳定性较差插入和删除操作只涉及指针修改,稳定性较好

标签:线性表,--,元素,next,链表,int,数据结构,节点,指针
From: https://blog.csdn.net/weixin_51933701/article/details/142949469

相关文章

  • AI生成论文软件的工作原理是什么?有哪些应用和前景?一文全知道!
    在当今信息爆炸的时代,快速获取高质量的文章和论文内容成为了许多人的需求。而AI论文生成工具作为AI技术的杰出代表,为我们提供了一种全新的解决方案。本文将以锐智AI为例深入探讨AI论文生成工具的工作原理、优势和应用前景,带您领略AI时代的灵感之门。AI论文生成工具是什么?AI......
  • C++学习路线(十四)
    指针的自增操作查看数组与指针的关系#include<iostream>usingnamespacestd;intmain(){ intages[]={18,20,22,25,28}; intlen=sizeof(ages)/sizeof(ages[0]); cout<<"使用数组的方式访问元素"<<endl; for(inti=0;i<len;i++){ co......
  • C++学习路线(十五)
    多级指针#include<iostream>usingnamespacestd;intmain(){ intblock1=888; int*block2=&block1; int**block3=&block2; int***block4=&block3; int****block5=&block4; cout<<"block2:"<<*block2......
  • Dirmap:一款高级Web目录文件扫描工具
    需求分析何为一个优秀的web目录扫描工具?经过大量调研,总结一个优秀的web目录扫描工具至少具备以下功能:并发引擎能使用字典能纯爆破能爬取页面动态生成字典能fuzz扫描自定义请求自定义响应结果处理...功能特点你爱的样子,我都有,小鸽鸽了解下我吧:支持n个target*n个p......
  • 预算不变,数据安全大升级!揭秘TRAID+的神奇之处
    假期过后,公司弥漫着一股低沉的气息,公司的CTO,小威,正面对着一个令人头疼的问题——如何在不增加预算的情况下,提高公司的数据安全性。如今各种勒索病毒层出不穷,或是因为硬盘的损坏或者员工的马虎使数据丢失都会让公司损失巨大。俗话说鸡蛋不要放在一个篮子里。面对着公司的海量数据......
  • 383_赎金信
    383_赎金信【问题描述】给你两个字符串:ransomNote和magazine,判断ransomNote能不能由magazine里面的字符构成。如果可以,返回true;否则返回false。magazine中的每个字符只能在ransomNote中使用一次。示例一:输入:ransomNote="a",magazine="b"输出:false示例二......
  • ARC156C 题解
    blog。显然答案为\(0\)不行。打表发现最优答案总为\(1\)。考虑构造取到\(1\)的下界。观察到,\(\text{LCS}\le1\)等价于去掉两序列都不存在的数后,两序列完全相反。于是有:在\(\{x\},\{y\}\)后增加两序列都不存在的数,不影响LCS。进行\(\{x\}\to\{a,x,b\},\{y\}\to\{b,......
  • 灾难恢复:邮箱数据库操作总结:整理 查询邮箱数据库大小和空白数据大小(重要文档)
    灾难恢复:邮箱数据库操作总结:整理查询邮箱数据库大小和空白数据大小(重要文档)邮箱数据库整理查询邮箱数据库大小和空白数据大小AvailableNewMailboxSpace是指的这个总空间里可以被“减肥”掉的空间。Get-MailboxDatabase db0* -Status|FTName,@{n="MailboxCount";e={(Get-M......
  • ssl证书时效监控脚本
    #!/bin/bash#定义要监控的域名列表DOMAINS=("example1.com""example2.com""example3.com")#设定警告阈值(天)WARNING_DAYS=30#获取当前日期CURRENT_DATE=$(date+"%Y-%m-%d")#输出头部信息echo"SSL证书过期时间监控报告-......
  • httprunner简要说明
    Httprunner1.简介面向HTTP(S)协议的测试通用框架,维护YAML/JSON脚本执行测试用例,最终都是转化为python文件执行,3.0以后官方建议直接转为维护python脚本特性继承Requests特性辅助函数debugtalk.py,实现动态计算逻辑测试分层,api层、测试用例层、测试套件支持Hook机制丰富的校......