Data Structure Learning: Starting with C Language Singly Linked List

After several weeks of C language learning, everyone has laid a certain foundation in programming. Starting today, we will move on to the next stage of learning ------ Data Structures.

1. Core Concepts

Program = Data Structure + Algorithm

  • Data Structure: The structure of data objects manipulated by a program.
  • Algorithm: The method by which a program manipulates data objects.

2. Metrics for Program Efficiency

Time Complexity

The proportional functional relationship between the growth of data volume and the growth of program running time is called the time complexity function, or Time Complexity for short.Common time complexities (in ascending order of efficiency):O(c), O(logn), O(n), O(nlogn), O(n²), O(n³), ..., O(2ⁿ)

Space Complexity

The proportional functional relationship between the growth of data volume and the growth of program memory usage is called Space Complexity.

3. Classification of Data Structures

3.1 Logical Structures

  • Linear Structure: One-to-one logical relationship (e.g., sequence list, linked list).
  • Tree Structure: One-to-many logical relationship (e.g., binary tree).
  • Graph Structure: Many-to-many logical relationship (e.g., undirected graph, directed graph).

3.2 Storage Structures

Sequential Storage

Advantages:

  • Convenient access to elements (via subscript offset).

Disadvantages:

  • Low efficiency of insertion and deletion (requires element movement).
  • Cannot utilize discrete small memory spaces.
Linked Storage

Advantages:

  • High efficiency of insertion and deletion (only modify pointer references).
  • Can make full use of discrete small memory spaces.

Disadvantages:

  • Inconvenient element access (requires sequential traversal).
  • Additional memory overhead for storing pointer fields.
Indexed Storage

Indexed storage is a storage method that creates an independent index table for the original data set ------ the index table records the key of the data and its physical address/position pointer in the original storage area. When searching for data, first find the address corresponding to the target key in the index table, then access the original data directly through the address instead of traversing the entire original data set.

Analogy: The table of contents of a book is the "index table", and the main text is the "original data set". To find a knowledge point, you first check the table of contents to get the page number (physical address), then turn to the corresponding page (direct access), instead of flipping through the book page by page.

Hashing Storage (Hash Table)

Hashing storage is a storage method that calculates the physical storage address of data directly from its key through a predefined hash function. When searching, the same hash function is used to calculate the key to get the address and access the data directly, without the need for an additional index table.

Analogy: The pickup code of a courier locker is the result of a hash function calculation. The key of the courier (data) is the waybill number. The courier company calculates the pickup code (storage address) through a hash function and places the courier in the corresponding locker. To pick up the courier, you enter the waybill number to recalculate the pickup code and open the locker directly, without checking any directory.

4. Key Content of Data Structure Learning

  1. Sequence List
  2. Linked List
  3. Sequential Stack
  4. Linked Stack
  5. Sequential Queue
  6. Linked Queue
  7. Binary Tree (Complete Binary Tree, Full Binary Tree)
  8. Hash Table
  9. Common Sorting and Searching Algorithms

5. Sequence List

5.1 Static Sequence List

Implemented with a fixed array in the stack/global area (e.g., int arr100), its capacity is fixed and immutable. It is only suitable for scenarios where the data volume is determined and is rarely used in actual development.

5.2 Dynamic Sequence List

Essentially equivalent to an array, it stores data by applying for heap memory and accesses all spaces through the start address + subscript offset.

Header File: seqlist.h

c

运行

复制代码
#ifndef __SEQLIST_H
#define __SEQLIST_H

typedef int DataType;

// Create a dynamic sequence list with specified length
DataType *CreatSeqlist(int len);
// Destroy the dynamic sequence list and release heap memory
void DestorySeqlist(DataType **pparr);

#endif
Source File: seqlist.c

c

运行

复制代码
#include <stdio.h>
#include <stdlib.h>
#include "seqlist.h"

DataType* CreatSeqlist (int len) {
	DataType *pret = NULL;

	pret = (DataType*)malloc(len * sizeof(DataType));
	if(pret == NULL) {
		perror("fail to malloc");
		return NULL;
	}

	return pret;
}

void DestorySeqlist(DataType **pparr) {
	free(*pparr);
	*pparr = NULL;
	return;
}

6. Linked List

6.1 Singly Linked List

A linked list is different from an array ------ array elements are stored in contiguous memory spaces, while the elements of a linked list (called nodes) are not necessarily contiguous. Each node contains a pointer that points to the next node, and we can access and traverse the linked list by storing the address of the next node in the previous one.

A Singly Linked List is a linked list that can only be traversed from the head to the tail (one-way traversal), and cannot be traversed in reverse.

Define the Node Structure

c

运行

复制代码
typedef int DataType;

// Node structure of singly linked list
typedef struct ListNode {
    DataType val;        // Data field
    struct ListNode *next; // Pointer field: point to the next node
} ListNode;

We rename the data type with typedef for code reusability ------ we can switch the data type of the linked list with just one line of code modification.

Singly linked lists are divided into headless linked lists and headed linked lists . Headless linked lists are cumbersome for traversal, search, insertion and other operations, so we first implement the headed linked list (with dummy node).

Basic Operations of Singly Linked List (with Dummy Node)

(1) Create a Dummy Node (Head Node)

c

运行

复制代码
// Create a dummy node (no valid data, only as the head of the linked list)
ListNode *CreateDummyNode(void) {
    ListNode *dummy = NULL;
    dummy = (ListNode*)malloc(sizeof(ListNode));
    if(dummy == NULL) {    // Return NULL if memory application fails (avoid wild pointer)
        perror("malloc dummy failed");   // Print error information for debugging
        return NULL;
    }
    dummy->val = 0;      // The value of dummy node is meaningless (set to 0 by default)
    dummy->next = NULL;  // Empty linked list: the successor of dummy node is NULL

    return dummy;
}
(2) Head Insertion

c

运行

复制代码
// Insert a new node at the head of the valid nodes (after the dummy node)
// Return 0 on success, -1 on failure
int InsertHeadNode(ListNode *dummy, DataType val) {
    if(dummy == NULL) {
        printf("error: dummy node is NULL\n");
        return -1;
    }
    ListNode *newNode = (ListNode*)malloc(sizeof(ListNode));
    if(newNode == NULL) {
        perror("malloc newNode failed");
        return -1;
    }
    newNode->val = val;
    // Core logic: link to successor first, then to predecessor (avoid linked list breakage)
    newNode->next = dummy->next;
    dummy->next = newNode;

    return 0;
}
(3) Tail Insertion

c

运行

复制代码
// Insert a new node at the tail of the valid nodes
// Return 0 on success, -1 on failure
int InsertTailNode(ListNode *dummy, DataType val) {
    if(dummy == NULL) {
        printf("error: dummy node is NULL\n");
        return -1;
    }
    ListNode *newNode = (ListNode*)malloc(sizeof(ListNode));
    if(newNode == NULL) {
        perror("malloc newNode failed");
        return -1;
    }

    newNode->val = val;
    newNode->next = NULL; // The tail node's successor must be NULL

    ListNode *p = dummy;
    // Traverse to find the last valid node
    while(p->next != NULL) {
        p = p->next;
    }
    p->next = newNode;
    
    return 0;
}
(4) Insert at Specified Position

Insert a new node after the pos-th valid node (pos starts from 1).

c

运行

复制代码
// Insert a new node after the pos-th valid node (pos starts from 1)
// Return 0 on success, -1 on failure
int InsertPosNode(ListNode *dummy, int pos, DataType val) {
    if(dummy == NULL || pos < 1) {
        printf("error: dummy node is NULL or pos is invalid\n");
        return -1;
    }
    ListNode *p = dummy->next;
    int curPos = 1;
    // Traverse to find the pos-th valid node
    while(p != NULL && curPos < pos) {
        p = p->next;
        curPos++;
    }
    
    if(p == NULL) {
        printf("error: pos out of range\n");
        return -1;
    }

    ListNode *newNode = (ListNode*)malloc(sizeof(ListNode));
    if(newNode == NULL) {
        perror("malloc newNode failed");
        return -1;
    }

    newNode->val = val;
    newNode->next = p->next;
    p->next = newNode;

    return 0;
}
(5) Delete Node by Specified Value

Delete the first valid node whose value is equal to the target value.

c

运行

复制代码
// Delete the first valid node with the specified value
// Return 0 on success, -1 on failure
int DeleteNode(ListNode *dummy, DataType val) {
    if(dummy == NULL) {
        printf("error: dummy node is NULL\n");
        return -1;
    }
    ListNode *pre = dummy;    // Predecessor node (start from dummy node)
    ListNode *cur = dummy->next; // Current node (start from the first valid node)
    while(cur != NULL && cur->val != val) {
        pre = cur;
        cur = cur->next;
    }
    
    if(cur == NULL) {
        printf("error: val %d not found\n", val);
        return -1;
    }

    // Skip the node to be deleted
    pre->next = cur->next;
    // Release heap memory and avoid wild pointer
    free(cur);
    cur = NULL;

    return 0;
}
(6) Traverse the Linked List

c

运行

复制代码
// Traverse and print all valid nodes of the linked list
void TraverseLinkList(ListNode *dummy) {
    if(dummy == NULL) {
        printf("error: dummy node is NULL\n");
        return;
    }
    ListNode *p = dummy->next;
    if(p == NULL) {
        printf("The linked list is empty\n");
        return;
    }
    printf("Valid nodes of the linked list: ");
    while(p != NULL) {
        printf("%d ", p->val);
        p = p->next;
    }
    printf("\n");
}
(7) Destroy the Linked List

c

运行

复制代码
// Destroy the entire linked list (including dummy node) and release all heap memory
// Use double pointer to set the original dummy node pointer to NULL
void DestroyLinkList(ListNode **dummy) {
    if(dummy == NULL || *dummy == NULL) return;
    ListNode *p = *dummy;
    ListNode *tmp = NULL;

    // Traverse and release all nodes one by one
    while(p != NULL) {
        tmp = p->next;
        free(p);
        p = tmp;
    }

    // Set the original pointer to NULL to avoid wild pointer
    *dummy = NULL;
}
(8) Find Node by Specified Value

c

运行

复制代码
// Find the first valid node with the specified value
// Return the node pointer on success, NULL on failure
ListNode *FindNode(ListNode *dummy, DataType val) {
    if(dummy == NULL) {
        printf("error: dummy node is NULL\n");
        return NULL;
    }
    ListNode *cur = dummy->next;
    while(cur != NULL) {
        if(cur->val == val) {
            return cur;
        }
        cur = cur->next;
    }

    printf("info: val %d not found in list\n", val);
    return NULL;
}
(9) Replace Node Value by Specified Value

Replace the value of the first valid node whose old value is oldVal with newVal.

c

运行

复制代码
// Replace the first valid node with oldVal to newVal
// Return 0 on success, -1 on failure
int ReplaceNode(ListNode *dummy, DataType oldVal, DataType newVal) {
    if(dummy == NULL) {
        printf("error: dummy node is NULL\n");
        return -1;
    }
    ListNode *target = FindNode(dummy, oldVal);
    if(target != NULL) {
        target->val = newVal;
        printf("Success: replace %d with %d\n", oldVal, newVal);
        return 0;
    }
    printf("Failed to replace: %d not found\n", oldVal);
    return -1;
}

The above are the basic operations of the singly linked list with a dummy node. Familiarize yourselves with the code and practice more by writing it repeatedly, and you will master it gradually.

We will continue to learn more advanced content of data structures in the next class. See you then!

相关推荐
傻啦嘿哟6 小时前
某招聘平台爬虫:爬取招聘岗位数据,分析各城市薪资水平
开发语言·爬虫·python
2501_933670796 小时前
2026秋招量化分析岗技能栈:Python、SQL、统计建模、回测项目怎么准备
开发语言·python·sql
Dola_Zou7 小时前
工厂智能排产软件算法保护与按产线计费实战
算法·自动化·软件工程·软件加密
李少兄7 小时前
JavaScript 数据类型完全指南
开发语言·javascript·ecmascript
Seoyoneh8 小时前
Agentic Workflow编排架构:云客服从“被动响应”迈向“主动执行”的技术实现
java·开发语言·架构
啦啦啦啦啦zzzz8 小时前
Logger的组装(c++20)
c++·算法·c++20
Tangyuewei8 小时前
Java 写 Agent:模型只是组件
java·开发语言
小玮看世界9 小时前
[Python]从合并区间到传感器融合区:合并区间在传感器区域融合的实际落地
开发语言·python
尾善爱看海9 小时前
前端算法与手写题集
前端·算法
繁星蓝雨10 小时前
C++的设计与演进大纲(如何从C语言一步步成长为C++)
c语言·开发语言·c++·c++历史·c++演进