线性表的链式实现--C语言

线性表,是数据结构中非常基础的一种数据结构,分顺序表和链式表两种;

顺序表就是我们常用的数组,链式表就是通过指针来串联元素;

数组与链表的区别:

1. 内存分配方式:
   - 数组:连续的内存块用于存储元素。在创建数组时,需要一次性分配足够大小的连续内存空间。
   - 链表:通过指针将离散的节点连接在一起存储元素。每个节点都可以在内存中的任何位置,它们使用指针来指向下一个节点。

2. 访问操作:
   - 数组:由于连续存储,对于已知索引的元素访问非常高效。通过索引计算偏移量,可以在O(1)时间内直接访问元素。
   - 链表:在访问元素时需要从头节点开始遍历整个链表,直到找到目标元素。平均情况下,链表的元素访问时间复杂度为O(n),其中n是链表长度。

3. 插入和删除操作:
   - 数组:插入和删除操作的效率相对较低。插入或删除元素后,需要移动其他元素以保持连续性,这可能需要较长的时间,尤其是在大型数组中。
   - 链表:插入和删除操作的效率相对较高。在链表中插入或删除一个节点只需要修改指针,不需要移动其他节点。

4. 动态大小调整:
   - 数组:数组的大小在创建时就确定了,无法动态地改变大小。如果需要更大的数组,必须重新分配内存并复制现有元素。
   - 链表:链表可以动态地增长或缩小。通过修改指针连接,可以在运行时轻松地添加或删除节点。

5. 存储空间开销:
   - 数组:由于需要连续的内存块,往往会浪费一部分内存空间,特别是当数组大小不确定或需求变化时。
   - 链表:由于使用离散的节点,不需要额外的存储空间来处理动态性,因此可以更有效地利用内存,但是指针也是占空间的。

链表的定义:

// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
};

链表的实现(附带详细注释):

//单链表
typedef struct Node {
    int data;
    struct Node *next;
} linkNode, *linkList;

//创建链表
linkList createList(int *arr, int size) {
    linkList L = (linkList) malloc(sizeof(linkNode)); //创建头结点
    L->next = NULL; //初始化头结点指针为空
    linkList p = L; //创建一个指针指向头结点

    for (int i = 0; i < size; ++i) { //遍历数组,创建新结点并插入链表尾部
        p->next = (linkList) malloc(sizeof(linkNode));
        p = p->next;
        p->data = arr[i]; //将数组元素赋值给新结点的数据域
        p->next = NULL; //将新结点的指针初始化为空
    }
    return L; //返回链表的头结点
}

//输出链表中的数据
void outputData(linkList L) {
    while (L->next) {
        L = L->next;
        printf("%d\t", L->data);
    }
}

//释放链表的内存空间
void freeLinklist(linkList L) {
    while (L->next) { //遍历链表,释放每个结点的内存空间
        linkList p = L->next;
        L->next = L->next->next;
        free(p);
    }
    free(L); //释放头结点的内存空间
}

//指定位置插入节点
void insertNode(linkList L, int position, int value) {
    int flag = 0;
    linkList p = L;
    while (p && flag < position) { //遍历链表,找到插入位置的前一个结点
        p = p->next;
        ++flag;
    }
    if (p && flag == position) { //如果找到位置,则插入新结点
        linkList newNode = (linkList) malloc(sizeof(linkNode));
        newNode->data = value; //将新结点的数据域赋值为要插入的值
        newNode->next = p->next; //将新结点的指针指向原来下一个结点
        p->next = newNode; //将前一个结点的指针指向新插入的结点
    } else {
        printf("无效位置\n");
    }
}

//删除指定位置的结点
void deleteNode(linkList L, int position) {
    int flag = 0;
    linkList p = L;
    while (p->next && flag < position) { //遍历链表,找到要删除的位置的前一个结点
        p = p->next;
        ++flag;
    }
    if (p->next && flag == position) { //如果找到位置,则删除结点
        linkList delNode = p->next; //定义一个指针指向要删除的结点
        p->next = delNode->next; //将前一个结点的指针指向要删除结点的下一个结点
        free(delNode); //释放要删除结点的内存空间
    } else {
        printf("无效位置\n");
    }
}

//删除链表中值为指定值的结点
int deleteNumber(linkList L, int target) {
    linkList p = L;
    int count = 0;
    while (p->next) {
        if (p->next->data == target) { //如果找到指定数的结点,则删除
            linkList delNum = p->next;
            p->next = delNum->next;
            free(delNum);
            ++count;
            continue; //继续下一次循环,防止删除多个连续的相同数的结点
        }
        p = p->next;
    }
    return count;
}

//修改指定位置的结点值
void modifyNode(linkList L, int position, int newNum) {
    int flag = 0;
    linkList p = L;
    while (p && flag < position) { //遍历链表,找到指定位置的结点
        p = p->next;
        ++flag;
    }
    if (p && flag == position) { //如果找到位置,则修改结点的值
        p->data = newNum; //修改结点的数据域为新的值
    } else {
        printf("无效位置\n");
    }
}

//查找指定值的结点位置
int selectPos(linkList L, int flagNum) {
    int pos = 0;
    linkList p = L->next;
    while (p) {
        if (p->data == flagNum) {
            printf("值的下标位置是:%d\n", pos);
            return pos;
        }
        p = p->next;
        ++pos;
    }
    return -1; 
}

//查询倒数第K个结点的值(双指针法)
void seekBack(linkList L, int pos) {
    if (L == NULL || pos <= 0) {
        return;
    }

    linkList slow = L;
    linkList fast = L;

    // 快指针先走word-1个节点
    for (int i = 0; i < pos; ++i) {
        fast = fast->next;
        if (fast == NULL) { //结点数小于word,退出
            return;
        }
    }
    
    //同时移动快指针和慢指针,直到快指针到达链表末尾
    while (fast) {
        slow = slow->next;
        fast = fast->next;
    }

    printf("%d\n", slow->data);
    //若是删除,则新建一个新节点保存待删节点,再free掉即可
    //linklist p = NULL;
    //p = slow->next;
    //slow->next = slow->next->next;
    //free(p);
}

//翻转链表
void linkListReversal(linkList L, int size) {
    linkList temp;
    linkList front = L;
    linkList later = NULL;

    while (front) { //意味着这个节点下面还有节点,还没有翻转完成
        temp = front->next;// 保存一下 front的下一个节点,因为接下来要改变front->next
        front->next = later;// 翻转操作

        later = front;// 更新 front和 later指针
        front = temp;
    }
    //return later;若用函数,返回值是later
    for (int i = 0; i < size; i++) {
        printf("%d ", later->data);
        later = later->next;
    }
}

//合并有序链表
linkList mergeLinklist(linkList L1, linkList L2) {
    linkList L3 = (linkList)malloc(sizeof(linkNode)); // 创建新链表头节点
    linkList p = L3; // 用于遍历新链表

    linkList p1 = L1->next; // 遍历链表L1
    linkList p2 = L2->next; // 遍历链表L2

    while (p1 && p2) {
        if (p1->data <= p2->data) {
            p->next = p1;
            p1 = p1->next;
        } else {
            p->next = p2;
            p2 = p2->next;
        }
        p = p->next;
    }

    // 将未遍历完的链表剩余部分连接到新链表尾部
    p->next = p1 ? p1 : p2;

    return L3;
}

//去除链表中的重复项
void deDuplicate(linkList L) {
    linkList fast = L;
    linkList slow = NULL;

    if(L == NULL){
        return;
    }

    while (fast->next != NULL) {
        if (fast->data == fast->next->data) {
            slow = fast->next;
            fast->next = fast->next->next;
            free(slow);
            continue;
//不可或缺,执行continue,跳出当前循环,就可以正确删除第二第三重复节点
//考虑以下情况:
//假设链表中有三个连续的重复节点,以及其他不重复的节点。
// 在删除第一个重复节点之后,由于没有continue语句,会继续执行循环中的下一条语句(即'fast = fast->next;'),
// 即移动fast指针到下一个节点。这样一来,会直接跳过第二个和第三个重复节点,无法删除它们。
        }
        fast = fast->next;
    }
}

本文为C语言基础知识的应用,要求熟练理解指针、结构体的定义,并正确的使用;

可以看出,C语言相较于C++,缺少了函数的封装性以及代码的重复利用性,之后会更新关于C++的基础知识,以及C++引以为傲的STL库的介绍相关的实现;

本文为C语言基础知识分享,没有一个程序员的代码是完美的,本人水平有限,如有错误,请补充和指正!

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
链式实现线性表C语言版本可以通过定义一个结构体来表示链表节点,结构体中包含存储的数据以及指向下一个节点的指针。具体实现如下: ```c #include <stdio.h> #include <stdlib.h> // 定义链表节点结构体 typedef struct ListNode { int data; // 数据域 struct ListNode *next; // 指针域,指向下一个节点 } ListNode; // 创建链表节点 ListNode* createNode(int value) { ListNode *node = (ListNode*) malloc(sizeof(ListNode)); node->data = value; node->next = NULL; return node; } // 插入节点到链表尾部 void insert(ListNode **head, int value) { ListNode *node = createNode(value); if (*head == NULL) { *head = node; } else { ListNode *current = *head; while (current->next != NULL) { current = current->next; } current->next = node; } } // 打印链表 void printList(ListNode *head) { ListNode *current = head; while (current != NULL) { printf("%d ", current->data); current = current->next; } printf("\n"); } int main() { ListNode *head = NULL; insert(&head, 1); insert(&head, 2); insert(&head, 3); printList(head); // 输出:1 2 3 return 0; } ``` 以上代码中,通过`createNode`函数创建一个新的节点,并返回该节点的指针。`insert`函数用于将节点插入链表的末尾,如果链表为空,则直接将该节点赋值给链表的头指针,否则找到链表的末尾节点,将其next指针指向新节点。`printList`函数用于遍历链表并打印每个节点的数据。在`main`函数中,通过调用`insert`函数将数据插入链表,然后通过调用`printList`函数打印链表的数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值