数据结构1.单链表

链表与数组

在编程语言中,数组数据结构(array data structure),简称数组(Array),是一种数据结构,是数据元素(elements)的集合。有限个相同类型的元素按顺序存储,用一个名字命名,然后用编号区分他们的变量的集合;这个名字称为数组名,编号称为下标。

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不同与数组的是,并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储。

一、链表与数组的优缺点

首先让我们来介绍下链表与数组之间的优缺点:

// 数组的缺点:
//
//    1.一旦数组定义,则大小固定,无法进行修改(数组的大小)。
//    2.数组插入和删除的效率太低,时间复杂度O(n)。
//    
// 数组的优点:
//    
//    1.下标访问,速度快,时间复杂度是O(1)
//
//
//
// 链表:
//   
// 链表的优点:
//   
//    1.资源允许的情况下,规模可以不断的增长或减小。
//    2.删除和添加效率高,O(1)
//
// 链表的缺点:
//
//    链表的遍历过程效率比较低。

为什么会有这样的差异呢。
接下来我们一步步来看。

二、数据结构类型的定义

数组的定义:
//方法1
int array1[10] = {0};    

//方法2
int array2[] = {12, 23, 34, 45, 56, 67, 78};  

//方法3
int *array3 = NULL;
array3 = (int *)malloc(sizeof(int) * 100);   
if(array3 != NULL){
     fprintf(stderr, "the memory is full!\n");
     exit(1);
}

这里,我们的array1array2都在定义时直接分配了上的内存空间;
array3是一个int类型的指针,指向我们在上用malloc分配的4bytes ×100 = 400 bytes大小的空间。

我们的数组虽然在O(1)的时间复杂度访问下标进行数据存取查找,可是一般数组定义后,大小不能够进行动态变化,且插入删除效率过低,时间复杂度为O(n).
所以,为了弥补这些不足,我们又学习到了链表。

链表的定义

链表是一种物理存储单元上非连续、非顺序的存储结构,链表由相同结构的链表节点构成,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。可以关注链表节点的结构:

typedef struct List_node
{
    int               data;    //数据域
    struct List_node *next;    //指针域
}List_node;

typedef List_node *List_head;
  1. 链表节点的两个组成部分:
    我们可以看到链表节点主要分为两大部分:数据域和链域。
    数据域是我们最要存储的内容,而链域则代表着一种指针。

  2. 如何把两个节点进行连接:
    这个指针指向类型为链表节点,这样每一个节点就可以记录下下一个节点的位置,从而把原本毫不相干的链表节点串接起来。

dlist

一个链表节点定义;

    typedef struct Node{
        //数据域
        char name[20];  //姓名
        char sex;       //m 男 w 女 性别
        char age;       //年龄
        char jobs[20];  //职业
        //链域
        struct Node *next;
    }Node;

再申请一个链表节点;

    Node node1 = {0};

    Node *p_node = (Node *)malloc(sizeof(Node));

    node1.next = p_node;

malloc 函数:
void *malloc(int size);
说明:malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void* 类型。
man malloc可在bash中查看帮助文档。

三、链表的常见接口

我们把可以对链表的操作是在.h文件中进行声明的,我们叫这样的声明为接口,如下是单链表的接口声明:

/* list.h */
/*
 * 带头节点的链表
 * */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#define TRUE     (1)
#define FALSE    (0)

typedef unsigned char Boolean;

//定义链表节点类型
typedef struct List_node{
    int               data;    //数据域  //4
    struct List_node *next;    //指针域  //32位:4 //64位:8
}List_node;


typedef List_node   *List_head;   

//链表的接口
//  常见接口:
//     1.初始化
//     2.销毁
//     3.增加
//     4.删除
//     5.查找
//     6.修改
//     7.排序
//     8.显示
List_head init_list(void)                                        ;   //1. 链表的初始化
void      destroy_list(List_head *head)                          ;   //2. 链表的销毁
Boolean   push_front(List_head head, int value)                  ;   //3.1 头部添加
Boolean   push_back(List_head head, int value)                   ;   //3.2 尾部添加(效率低,从头找到尾才能添加)
Boolean   pop_front(List_head head)                              ;   //4.1 头部删除
Boolean   pop_back(List_head head)                               ;   //4.2 尾部删除(效率低)
Boolean   find_node(List_head head, int value, List_node **node) ;   //5. 链表的查找
void      modify_node(List_node *node, int value)                ;   //6.1 链表节点的修改
Boolean   insert_node(List_head head, int index, int value)      ;   //6.2 链表节点的插入(下标)
void      sort_list_ascend(List_head head)                       ;    //7.1 链表升序
void      sort_list_descend(List_head head)                      ;    //7.2 链表降序
void      show_list(List_head head)                              ;    //8.1 显示链表信息
int       get_list_length(List_head head)                        ;    //8.2 链表的长度

我们可以通过定义 包裹函数 来缩短程序。每个包裹函数完成实际的函数调用,检查返回值,并在发生错误时终止进程。
包裹函数其实就是封装函数,调用一个函数来实现这个功能。既然我们会经常用到一些函数并且还要进行错误处理,那么,我们可以将这些函数封装起来,也放入头文件.h当中。

//包裹函数
static void *Malloc(size_t size);
static List_node *create_node(void);
static void swap(void *a, void *b, int length);

static void *Malloc(size_t size)
{
    void *result = malloc(size);
    if(result == NULL){   //失败情况处理
        fprintf(stderr, "the memory is full!\n");
        exit(1);
    }
    return result;
}

static List_node *create_node(void)
{
    //申请节点,并且对节点初始化(置为0)
    List_node *node = (List_node *)Malloc(sizeof(List_node));
    bzero(node, sizeof(List_node));

    return node;
}
static void swap(void *a, void *b, int length)
{
    void *temp = Malloc(length);

    memcpy(temp, a, length);
    memcpy(a, b, length);
    memcpy(b, temp, length);

    free(temp);
}

四、链表接口的实现

为了能够使用这些我们所提供的接口,如同菜名需要菜谱一样,我们要能够实现这些操作,可以在list.c中进行实现。


//
//接口实现
//

List_head init_list(void)                                           //1. 链表的初始化
{
    List_head head = NULL;

    head = create_node();

    return head;
}

void      destroy_list(List_head *head)                             //2. 链表的销毁
{
    List_node *p = NULL;
    List_node *q = NULL;

    if(head == NULL || *head == NULL){
        return ;
    } 

    //从链表头节点开始,依次对节点进行删除
    p = *head;
    while(p != NULL){
        q = p;
        p = p->next;
        free(q); 
    }

    *head = NULL;
}

Boolean   push_front(List_head head, int value)                      //3.1 头部添加
{
    List_node *node = NULL;

    if(head == NULL){    //判断链表是否存在
        return FALSE;
    }

    //生成链表节点并且赋初值
    node = create_node();
    node->data = value;

    node->next = head->next;
    head->next = node;

    head->data++;  
    return TRUE;
}

Boolean   push_back(List_head head, int value)                      //3.2 尾部添加(效率低,从头找到尾才能添加)
{
    List_node *p_node = NULL;

    if(head == NULL){    //参数检测
        return FALSE;
    }  

    //查找尾部节点的位置
    p_node = head;
    while(p_node->next != NULL){
        p_node = p_node->next;
    }

    //把新生成的节点追加到链表末尾
    p_node->next = create_node();
    p_node->next->data = value;

    head->data++;
    return TRUE;    
}

Boolean   pop_front(List_head head)                                  //4.1 头部删除
{
    List_node *p_node = NULL;

    if(head == NULL || head->next == NULL){
        //链表不存在或者没有有效节点
        return FALSE;
    }

    p_node = head->next;
    head->next = p_node->next;
    free(p_node);

    head->data--;
    return TRUE;
}

Boolean   pop_back(List_head head)                                   //4.2 尾部删除(效率低)
{
    List_node *p_node = NULL;

    //参数检测
    if(head == NULL || head->next == NULL){
        return FALSE;
    }

    //从头结点开始查找倒数第二个节点
    p_node = head;

    while(p_node->next->next != NULL){
        p_node = p_node->next;
    }
    //删除最后一个节点,把倒数第二个置为最后一个节点
    free(p_node->next);
    p_node->next = NULL;

    head->data--;
    return TRUE;
}




Boolean   find_node(List_head head, int value, List_node **node)    //5. 链表的查找
{
    List_node *p_node = NULL;

    if(head == NULL){
        return FALSE;
    }

    //从第一个有效节点开始查找值为value的节点
    p_node = head->next;
    while(p_node != NULL){
        if(p_node->data == value){    //找到value所在节点
            if(node != NULL){
                *node = p_node;
            }
            return TRUE;
        }
        p_node = p_node->next;
    }
    return FALSE;
}

void      modify_node(List_node *node, int value)                    //6.1 链表节点的修改
{
    node->data = value;
}

Boolean   insert_node(List_head head, int index, int value)   //6.2 链表节点的插入
{
    List_node *node = NULL;
    List_node *p_node = NULL;
    int count = index;

    if(head == NULL || index < 0 || index > head->data){
       //链表不存在并且下标不符合规则
       return FALSE;
    }

    //创建新的节点
    node = create_node();
    node->data = value;

    //寻找插入的位置
    p_node = head;
    while(count--){   //找到被插入节点的前一个节点
       p_node = p_node->next;
    }

    //插入新的节点
    node->next = p_node->next;
    p_node->next = node;
    head->data++;
    return TRUE;
}

void      sort_list_ascend(List_head head)                           //7.1 链表升序
{
    List_node *p_node = NULL;
    List_node *q_node = NULL;

    if(head == NULL || head->data < 2){
        return ;
    }

    for(p_node = head->next; p_node->next ; p_node = p_node->next){ 
        for(q_node = p_node->next; q_node; q_node = q_node->next){
            if(p_node->data > q_node->data){
                swap(p_node, q_node, (unsigned long)(&((List_node *)0)->next));
            }
        }
    }
}

void      sort_list_descend(List_head head)                          //7.2 链表降序
{
    List_node *p_node = NULL;
    List_node *q_node = NULL;

    if(head == NULL || head->data < 2){
        return ;
    }

    for(p_node = head->next; p_node->next ; p_node = p_node->next){ 
        for(q_node = p_node->next; q_node; q_node = q_node->next){
            if(p_node->data < q_node->data){
                swap(p_node, q_node, sizeof(List_node) - sizeof(List_node *));
            }
        }
    }
}

void      show_list(List_head head)                                 //8.1 显示链表信息
{
    List_node *p_node = NULL;

    if(head == NULL){
        return ;
    }

    for(p_node = head->next; p_node != NULL; p_node = p_node->next){
        printf("%d  ", p_node->data);
    }
    printf("\n");
}

int       get_list_length(List_head head)    //8.2 链表的长度
{
    if(head == NULL){
        return -1;
    }
    return head->data;
}

设计完这个单链表的接口与实现之后,我们要对相应功能进行检测。
测试代码如下:

//主函数-各个函数的测试
int main(int argc, char ** argv)
{
    int i = 0;
    List_head head = NULL;
    head = init_list();
    List_node *node = create_node();

    printf("\n头部插入:\n"); 
    for(i = 0 ; i < seed ; i++ )
    {
        push_front(head,rand()%100);
    }
    show_list(head);                   

    printf("\n尾部插入:\n"); 
    for(i = 0 ; i < seed ; i++ )
    {
        push_back(head,rand()%100);
    }
    show_list(head);                   

    printf("\n头部删除:\n"); 
    pop_front(head);
    show_list(head);                 

    printf("\n尾部删除:\n"); 
    pop_back(head);
    show_list(head);                 

    printf("\n插入节点:\n"); 
    insert_node(head, 3, 100);
    show_list(head);                 

    find_node(head, 77,&node);

    printf("\n升序排序:\n");
    sort_list_ascend(head);
    show_list(head);                 

    printf("\n降序排序:\n");
    sort_list_descend(head);
    show_list(head); 

    printf("\n修改节点数据:\n");
    node = head->next->next; 
    modify_node(node, 777);
    show_list(head); 

    printf("\nthe length of this list :%d\n",get_list_length(head));

    destroy_list(&head);
    return 0;
}

运行结果:

root@aemonair# ./my_head_list 
链表初始化成功!

头部插入:
当前链表如下:
93 15 77 86 83 

尾部插入:
当前链表如下:
93 15 77 86 83 35 86 92 49 21 

头部删除:
当前链表如下:
15 77 86 83 35 86 92 49 21 

尾部删除:
当前链表如下:
15 77 86 83 35 86 92 49 

插入节点:
当前链表如下:
15 77 86 100 83 35 86 92 49 

the node is found !

升序排序:
当前链表如下:
15 35 49 77 83 86 86 92 100 

降序排序:
当前链表如下:
100 92 86 86 83 77 49 35 15 

修改节点数据:
当前链表如下:
100 777 86 86 83 77 49 35 15 

the length of this list :9
链表销毁完毕!

测试程序2:


int main(int argc, char **argv)
{
    List_head head = init_list();   //链表的初始化
    int i = 0;
    int value = 0;
    List_node *find = NULL;

    for(i = 0; i < 10; ++i){    //尾部追加10个元素
        push_front(head, rand() % 100);
    }

    pop_front(head);

    pop_back(head);

    show_list(head);   //显示链表信息

    printf("the count of list:%d\n", get_list_length(head));

    printf("你要找哪个节点:?\n");
    scanf("%d", &value);

    find_node(head, value, &find);
    if(find == NULL){
        printf("the %d is not found!\n", value);
    }else{
        printf("found! the value is %d\n", find->data);
    }
    sort_list_ascend(head);
    show_list(head);   //显示链表信息

    sort_list_descend(head);
    show_list(head);   //显示链表信息

    destroy_list(&head);
    return 0;
}

总结:

以上,我们关于单链表的基本操作和基本接口的实现。
对于链表的指针域在交换两个的时候很容易出现问题,之后还会进行进一步的详解。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值