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!

相关推荐
和裕14 分钟前
全纸结构重型纸箱能否满足 1 吨以上设备出口熏蒸豁免要求?通关合规性全解析
大数据·运维·网络·人工智能·算法
tangguofeng1 小时前
不带头结点的链队操作集(不含InitFlag)(C语言版)
算法
weixin_440730501 小时前
函数return、参数、参数组、递归函数、高阶函数等小结
开发语言·python
会员果汁1 小时前
函数调用和变长参数
c语言
sugarzhangnotes1 小时前
【无标题】
开发语言·人工智能·python
geovindu1 小时前
rust: Composite Pattern
开发语言·后端·设计模式·rust·组合模式
苏打豆2 小时前
CRISP源码阅读——基于ROS2力矩反馈控制器与速度加速度零空间
c++·线性代数·算法·矩阵·机器人
蛋蛋的就会顺顺的3 小时前
hot100——矩阵
java·数据结构·算法·leetcode·力扣
老歌老听老掉牙3 小时前
麻花钻切屑形态演变的力学机制与临界条件分析
python·算法·钻头
周杰偷奶茶3 小时前
【Java】数组的定义和使用(附管理系统实战案例)
java·开发语言