📜  使用数据结构 - C 编程语言(1)

📅  最后修改于: 2023-12-03 15:22:24.316000             🧑  作者: Mango

使用数据结构 - C 编程语言

简介

C 语言是一种广泛使用的编程语言,具有高度的可移植性和效率。C 语言中支持的数据结构有数组、链表、堆栈、队列、树、图等。在 C 语言中,数据结构是由程序员自己定义和实现的,这使得程序员有很大的自由度和灵活性。

本文将介绍 C 语言中常用的数据结构以及它们的实现方法。

数组

数组是 C 语言中最基本的数据结构之一。它是由同一数据类型的元素组成的有序集合。数组可以通过下标来访问元素,下标从 0 开始计数。

声明和初始化数组

使用 C 语言声明和初始化数组的语法如下:

数据类型 数组名称[数组长度] = {元素1, 元素2, ……, 元素n};

例如,声明和初始化一个包含 5 个整型元素的数组:

int arr[5] = {1, 2, 3, 4, 5};
读取和修改数组元素

可以使用下标来访问数组中的元素。例如,要访问数组中的第一个元素,可以使用以下语法:

int firstElement = arr[0];

要修改数组中的某个元素,可以使用以下语法:

arr[0] = 10;
数组的应用

数组常常被用于存储一组数据,比如存储学生的成绩、员工的工资等等。

链表

链表是由若干个结点组成的数据结构,每个结点包含一个值和一个指向下一个结点的指针。链表可以分为单向链表和双向链表,单向链表只包含一个指向下一个结点的指针,而双向链表则包含一个指向上一个结点的指针和一个指向下一个结点的指针。

链表的定义

定义一个单向链表的结构体如下:

struct ListNode {
    int val;
    struct ListNode *next;
};
创建链表

创建链表可以采用头插法和尾插法,头插法是将新结点插入链表的头部,尾插法则是将新结点插入链表的尾部。

头插法创建链表

struct ListNode* createList(int a[], int n) {
    struct ListNode *head, *newNode;
    head = (struct ListNode*)malloc(sizeof(struct ListNode));
    head->next = NULL;
    for(int i = n - 1; i >= 0; i--) {
        newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
        newNode->val = a[i];
        newNode->next = head->next;
        head->next = newNode;
    }
    return head->next;
}

尾插法创建链表

struct ListNode* createList(int a[], int n) {
    struct ListNode *head, *tail, *newNode;
    head = (struct ListNode*)malloc(sizeof(struct ListNode));
    head->next = NULL;
    tail = head;
    for(int i = 0; i < n; i++) {
        newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
        newNode->val = a[i];
        newNode->next = NULL;
        tail->next = newNode;
        tail = newNode;
    }
    return head->next;
}
遍历链表

可以用while循环分别对链表进行遍历。

void traverse(struct ListNode* head) {
    struct ListNode *p = head;
    while(p != NULL) {
        printf("%d ", p->val);
        p = p->next;
    }
}
链表的应用

链表常常被用于实现栈、队列以及图等数据结构。

栈和队列

栈和队列是两种常用的数据结构。栈具有“先进后出”的特点,而队列具有“先进先出”的特点。

栈的定义和实现

栈是由一系列元素组成的集合,只能在一端进行插入和删除操作,这一端称为栈顶。插入操作称为入栈,删除操作称为出栈。

栈的定义

可以使用数组或链表来实现栈。

数组实现:

#define MAX_SIZE 100

struct Stack {
    int data[MAX_SIZE];
    int top;
};

void InitStack(struct Stack *s) {
    s->top = -1;
}

bool Push(struct Stack *s, int x) {
    if(s->top == MAX_SIZE - 1)
        return false;
    s->top++;
    s->data[s->top] = x;
    return true;
}

bool Pop(struct Stack *s, int *x) {
    if(s->top == -1)
        return false;
    *x = s->data[s->top];
    s->top--;
    return true;
}

链表实现:

struct ListNode {
    int val;
    struct ListNode *next;
};

struct Stack {
    struct ListNode *top;
};

void InitStack(struct Stack *s) {
    s->top = NULL;
}

bool Push(struct Stack *s, int x) {
    struct ListNode *newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    if(newNode == NULL)
        return false;
    newNode->val = x;
    newNode->next = s->top;
    s->top = newNode;
    return true;
}

bool Pop(struct Stack *s, int *x) {
    if(s->top == NULL)
        return false;
    struct ListNode *p = s->top;
    *x = p->val;
    s->top = p->next;
    free(p);
    return true;
}
队列的定义和实现

队列是由一系列元素组成的集合,插入元素在队列的尾部,删除元素在队列的头部。

队列的定义

可以使用数组或链表来实现队列。

数组实现:

#define MAX_SIZE 100

struct Queue {
    int data[MAX_SIZE];
    int front, rear;
};

void InitQueue(struct Queue *q) {
    q->front = q->rear = 0;
}

bool EnQueue(struct Queue *q, int x) {
    if((q->rear + 1) % MAX_SIZE == q->front)
        return false;
    q->data[q->rear] = x;
    q->rear = (q->rear + 1) % MAX_SIZE;
    return true;
}

bool DeQueue(struct Queue *q, int *x) {
    if(q->rear == q->front)
        return false;
    *x = q->data[q->front];
    q->front = (q->front + 1) % MAX_SIZE;
    return true;
}

链表实现:

struct ListNode {
    int val;
    struct ListNode *next;
};

struct Queue {
    struct ListNode *front;
    struct ListNode *rear;
};

void InitQueue(struct Queue *q) {
    q->front = q->rear = NULL;
}

bool EnQueue(struct Queue *q, int x) {
    struct ListNode *newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    if(newNode == NULL)
        return false;
    newNode->val = x;
    newNode->next = NULL;
    if(q->front == NULL)
        q->front = newNode;
    else
        q->rear->next = newNode;
    q->rear = newNode;
    return true;
}

bool DeQueue(struct Queue *q, int *x) {
    if(q->front == NULL)
        return false;
    struct ListNode *p = q->front;
    *x = p->val;
    q->front = p->next;
    free(p);
    if(q->front == NULL)
        q->rear = NULL;
    return true;
}
栈和队列的应用

栈常常被用于实现表达式求值、函数调用、深度优先搜索等算法。

队列常常被用于实现广度优先搜索、缓存等算法。

树是一种非线性的数据结构,由若干个结点和连接它们的分支组成。每个结点可以有若干个子结点和一个父结点。

树的定义和遍历

树是由一系列结点和分支组成的,树的顶部结点称为根结点,除了根结点以外的每个结点有一个父结点和若干个子结点。没有子结点的结点称为叶子结点。

遍历树的方式有前序遍历、中序遍历和后序遍历,它们的区别在于访问结点的时机不同。

树的定义

定义一个二叉树的结构体如下:

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

树的遍历

前序遍历:

void PreOrder(struct TreeNode* root) {
    if(root == NULL)
        return;
    printf("%d ", root->val);
    PreOrder(root->left);
    PreOrder(root->right);
}

中序遍历:

void InOrder(struct TreeNode* root) {
    if(root == NULL)
        return;
    InOrder(root->left);
    printf("%d ", root->val);
    InOrder(root->right);
}

后序遍历:

void PostOrder(struct TreeNode* root) {
    if(root == NULL)
        return;
    PostOrder(root->left);
    PostOrder(root->right);
    printf("%d ", root->val);
}
树的应用

树常常被用于实现二叉查找树、AVL树、红黑树等算法。

图是由若干个结点和它们之间连线组成的数据结构。图可以分为有向图和无向图,有向图的边有一个方向,无向图的边没有方向。图中的结点称为顶点,连接两个顶点的线称为边。

图的定义和遍历

图可以使用邻接表、邻接矩阵和关联矩阵等来表示。

图的定义

定义一个邻接表表示的无向图如下:

struct ListNode {
    int val;
    struct ListNode *next;
};

struct Edge {
    int to;
    struct Edge *next;
};

struct Graph {
    struct ListNode *adjList;
    int *visited;
};

图的遍历

深度优先搜索:

void DFS(struct Graph *g, int u) {
    struct ListNode *p;
    g->visited[u] = 1;
    printf("%d ", u);
    for(p = g->adjList[u].next; p != NULL; p = p->next) {
        int v = p->val;
        if(!g->visited[v])
            DFS(g, v);
    }
}

广度优先搜索:

void BFS(struct Graph *g, int u) {
    struct ListNode *p;
    struct Queue q;
    InitQueue(&q);
    g->visited[u] = 1;
    printf("%d ", u);
    EnQueue(&q, u);
    while(q.front != NULL) {
        int u = q.front->val;
        DeQueue(&q, &u);
        for(p = g->adjList[u].next; p != NULL; p = p->next) {
            int v = p->val;
            if(!g->visited[v]) {
                g->visited[v] = 1;
                printf("%d ", v);
                EnQueue(&q, v);
            }
        }
    }
}
图的应用

图常常被用于实现最短路径算法、最小生成树算法等。