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 arr[100]), 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!

相关推荐
2401_832131952 小时前
模板编译期机器学习
开发语言·c++·算法
ValhallaCoder2 小时前
hot100-子串
数据结构·python·算法
独自破碎E2 小时前
LCR005-最大单词长度乘积
java·开发语言
ygklwyf2 小时前
无向图的连通性之割点/边,点/边双连通分量
算法·无向图·圆方树
2401_838472512 小时前
单元测试在C++项目中的实践
开发语言·c++·算法
naruto_lnq2 小时前
移动语义与完美转发详解
开发语言·c++·算法
梦想画家2 小时前
掌控并发的灵魂:Go context 从入门到实战全解析
开发语言·golang
MicroTech20252 小时前
自适生长的点云智能:MLGO微算法科技基于双阈值与DDM的仿生式配准算法
科技·算法
还是奇怪2 小时前
Python第四课:循环与数据结构深度解析
数据结构·windows·python·青少年编程·循环