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!

相关推荐
.千余3 小时前
【C++】C++ set 与 multiset 完全指南:关联式容器入门
开发语言·c++·笔记·学习·其他
c++之路6 小时前
CMake 系列教程(二):基础命令详解
开发语言·c++
南境十里·墨染春水11 小时前
C++ 工厂模式:从入门到进阶,彻底掌握对象创建的艺术
开发语言·c++·算法
@insist12311 小时前
系统架构设计师-实时性评价、调度算法与内核架构选型
算法·架构·系统架构·软考·系统架构设计师·软件水平考试
JosieBook12 小时前
【数据库】时序预测能力的分级进化:TimechoAI如何让每一类用户都能精准预见未来
java·开发语言·数据库
加号312 小时前
【C#】 文件与目录管理:创建、删除操作的技术解析
开发语言·c#
diving deep13 小时前
脚本速览-python
开发语言·python
一生了无挂13 小时前
Java处理JSON技巧教学(从基础到高阶实战全覆盖)
java·开发语言·json
swordbob13 小时前
Spring 单例 Bean 是线程安全的吗?
java·开发语言