C 语言学习(9) ---- 单向链表

链表概念

链表一般分为单向链表,双向链表,环形链表,链表实际是线性表的顺序存储结构,和数组不同的是,它用一组任意的存储单元来存储线性表中的数据,存储单元不是连续的
链表的长度不是固定的,链表这一数据结构的特点可以非常方便的实现节点的插入和删除操作
链表的每一个元素称为一个节点,每个节点可以储存在内存中的不同位置,每个节点除了存储每个节点本身的信息外,还要存储下一个节点的位置信息
单链表中,每个节点包含一个指向下一节点的指针变量,链表最后一个节点指针字段的值为NULL,提示链表不再包含后续节点,找到链表的第一个节点后,指针就可以带你访问剩余的所有节点
单链表使用根指针(root pointer)指向链表的第一个节点,注意根指针只是一个指针,它不包含任何数据

注意:链表只能顺序访问,不能随机访问,链表这种存储方式最大的缺点是容易出现断链,一旦某个节点的指针域数据丢失,该节点后的数据也会完全丢失

  1. 链表和数组比较
    数值是线性表的一种顺序存储方式,优点是使用直观,支持快速,随机访问
    缺点是对数组插入和删除需要移动大量的数组元素,定义时必须指定数组的长度,一旦运行,长度就不能再改变,空间效率低

单链表的初始化

typedef struct Node_s {
    struct Node_s *next;
    uint32_t value;
} stNode;

// root pointer point to first element
stNode* root = NULL;

static stNode *createListNode(uint32_t data) {
    stNode *list = (stNode*)malloc(sizeof(stNode));
    list->value = data;
    list->next = NULL;
    return list;

单链表插入操作

  1. 插入单链表头部
  2. 插入单链表的末尾
单链表_头插和尾插.jpg

单链表的头插和尾插都只需要一个指针

  • 插入单链表头部,获取根指针值就可以获取链表首元素的地址,这种情形需要改变根指针变量的值
  • 插入单链表尾部,根据根指针变量遍历链表,直到current->next 为NULL,就可以认为指向了最后一个节点
static void insertListNodeBack(stNode *rootp, stNode *lstn) {
    stNode *current = rootp;
    while (current->next != NULL) {
        current = current->next;
    }
    current->next = lstn;
    lstn->next = NULL;
}

static void insertListNodeFront(stNode **rootp, stNode *lstn) {
    lstn->next = *rootp;
    *rootp = lstn;
}
  1. 通用的插入函数
    通用的插入函数考虑了插入首节点,尾结点还有中间节点的情形,使用两个指针顺序遍历链表,初始化时 previous 设置为NULL,current 指向第一个节点


    单链表_中间插入.jpg
int insertListNodeCommon(stNode **rootp, stNode *lstn) {
    //current 指向第一个节点
    stNode *current = *rootp;
    stNode *previous = NULL;

    while (current != NULL && current->value < lstn->value) {
        previous = current;
        current = current->next;
    }
    // 此时 current->value >= lstn->value  lstn 应该插入 current 指针之前
    lstn->next = current;

    // 此时插入的是首节点
    if (previous == NULL)
        *rootp = lstn;
    else
        previous->next = lstn;

    return TRUE;
}

单链表删除操作

  1. 删除单链表的首元素
  2. 删除单链表的末尾元素
  • 删除单链表的首元素使用一个指针即可,通过 rootp 指向第一个节点,但是要改变根指针的指向
  • 删除单链表的末尾元素,需要使用两个指针,因为需要将倒数第二个节点的 next 字段重新指向新的元素
单链表的头部和尾部删除.jpg
void deleteNodefront(stNode **rootp)
{
    stNode *deleteNode = *rootp;
    *rootp = deleteNode->next;
    free(deleteNode);
    deleteNode = NULL;
}

void deleteNodeback(stNode *rootp)
{
    stNode *previous = NULL;
    stNode *current = rootp;

    while (current->next != NULL) {
        previous = current;
        current = current->next;
    }
    // 此时 current->next = NULL current 指向最后一个节点 previous 指向倒数第二个节点
    free(current);
    current = NULL;
    previous->next = NULL;
}

3.通用的删除函数
通用的删除函数需要使用两个指针,包含了头尾和中间的情形:


单链表的中间删除.jpg
int deleteListNodeCommon(stNode **rootp, uint32_t value) {
    stNode *current = *rootp;
    stNode *previous = NULL;

    while (current->next != NULL && current->value != value) {
        previous = current;
        current = current->next;
    }

    // 如果遍历到最后一个节点, 最后一个节点的值任然不符合
    if (current->next == NULL && current->value != value) {
        printf("cannot find element value %d \n", value);
        return FALSE;
    }

    // previous = NULL current = *rootp 删除第一个节点
    // previous != NULL 中间节点 或者 尾结点
    if(previous != NULL) {
        previous->next = current->next;
    } else {
        *rootp = current->next;
    }

    free(current);
    current == NULL;
    return TRUE;
}

单链表遍历操作

只要获取到根指针,就可以顺次遍历到单链表的所有节点:

static void dumpAllListNode(stNode *plist) {
    printf("dump list: ");
    stNode *p = plist;
    while (p != NULL) {
        printf(" %d", p->value);
        p = p->next;
    }
    printf("\n");
}

单链表的反转操作

反转操作,实际就是将链表整体反过来,头变为尾,尾变成头,实现反转操作的算法有四种:
迭代反转法、递归反转法、就地逆置法和头插法

  1. 迭代反转法
    该算法的思是从当前链表的第一个节点开始,一直遍历到最后一个节点,定义三个指针,顺次指向三个节点,每次三个指针都一起移动 如下图所示:


    迭代反转法翻转链表.jpg

定义三个指针分别为 begp,midp,endp

  • endp 的 next 域保持不变,这样可以保证找到下一节点
  • 改变 midp 的 next 域指向 begp,midp 的移动依靠 endp的赋值
  • begp 的赋值依靠 midp
int iteration_reverse(stNode **rootp) {
    // current 指向第一个节点
    stNode *current = *rootp; 
    if (current == NULL) {
        printf("empty list !!\n");
        return FALSE;
    } 
    stNode *begp = NULL;
    stNode *midp = current;
    stNode *endp = current->next;
    while (1) {
        midp->next = begp;
        if (endp == NULL) {
            break;
        }

        begp = midp;
        midp = endp;
        endp = endp->next;
    }
    //最后修改 head 头指针的指向
    *rootp = midp;
    return TRUE;
}

单链表反转操作文章:http://c.biancheng.net/view/8105.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容