1、链表

链表(linked list)即使是一些包含数据的独立数据结构的(Node)集合.

链表中的每个节点通过链或指针链接在一起.

程序通过指针访问链表中的节点.

节点通常是动态分配的,但也有由节点数组构建的链表(即使这样,程序也是通过指针来遍历链表).

1.1 单链表

单链表中,每个节点包含一个指向链表下一节点的指针.链表最后一个节点的指针字段的值为NULL.提示链表后面不再有其他节点.

根指针,根指针指向链表的第一个节点,根指针只是一个指针,不包含任何数据.

//但链表节点的结构
typedef struct NODE {
struct Node *link;
int value;
} Node;

单链表的一些性质

链表不一定是顺序存储的,它的节点可能分部于内存的各个地方.

单链表可以通过链表从开始遍历链表知道结束位置,但无法从相反方向进行遍历.

1.2 在单链表中插入

我们怎么才能把一个新节点插入到有序单链表中呢?加入要把12插入到单链表.(如果链表中的值为 5, 10, 15)

思路:从链表的起始位置开始,跟随指针直到找到第一个值大于7的节点,然后把这个新值插入到那个节点之前的位置.

有一个问题,当我们找到第一个大于12的节点时,指针可能指向了15,我们无法直到已经遍历过的指针了,我们也无法返回.解决这个问题的方法就是始终保存一个指向链表当前节点之前的那个节点指针.

//插入到一个有序的单链表,函数的参数是一个指向链表第一个节点的指针以及需要插入的值.
#include <stdlib.h>
#include <stdio.h>
#include "ssl_node.h" #define FALSE 0
#define TRUE 1 int ssl_insert( Node *current, int new_value ) {
Node *previous;
Node *new;
//寻找正确的插入位置,方法是按顺序访问链表,直到到达其值大于或等于新插入的值
while( current->value < new_value ){
previour = current;
current = current->link;
}
//为新节点分配内存,并把新值存储到新节点中,如果内存分配失败,函数返回False
new = (Node *)malloc( sizeof( Node ) );
if( new == NULL )
return FALSE;
new->value = new_value;
//把新节点插入到链表中,并返回TURE
new->link = current;
previous->link = new;
return TURE;
}
//调用
result = ssl_insert( root, 12 );

以上函数是存在问题的

插入20时,while 会越过链表的尾部.并对一个NULL指针执行间接访问操作. 我们没有对current 指针判空,在while语句中直接对current取value是不对的.对空指针间接引用是非法的.

所以 while( current != NULL & current->value < value ) 时继续循环

插入3时, 为了在链表的起始位置插入一个节点,函数必须修改指针.但是,函数不能反问变量root. 修正这个问题最容易的方法是把 root 声明为全局变量,这样插入函数就能修改它.不幸的是,这是最坏的一种方法.因为这样一来函数只对这个链表起作用了

稍好的解决方法是把一个指向root的指针作为参数传递给函数.然后使用间接访问. 函数不仅可以获得 root (指向链表第一个节点的指针,也就是根指针)的值,也可以向它存储一个新的指针值. 这个参数的类型是什么呢? root 是一个指向 Node 的指针, 所以参数的类型应该是Node **, 也就是一个指向Node 的指针的指针.

#include <stdlib.h>
#include <stdio.h>
#include "ssl_node.h" #define FALSE 0
#define TRUE 1
//插入到一个有序链表,函数的参数是一个指向链表根指针的指针,以及一个需要插入的新值
int ssl_insert( Node **rootp, int new_value) {
Node *current;//当前指针
Node *previous;//前一个指针
Node *new;//新节点指针 //得到一个指向第一个节点的指针
current = *rootp;
previous = NULL;//初始化previous //寻找正确的插入位置,方法是按序访问链表,直到达到一个其值大于或等于新值的节点
while ( current != NULL && current->value < new_value) {
previous = current;
current = current->link;
} //为新节点分配内存,并把新值存储到新的节点,如果内存分配失败.函数返回FALSE
new = (Node *)malloc( sizeof( Node ));
if (new == NULL) {
return FALSE;
}
new->value = new_value; //把新节点插入到链表中,并返回 TURE
new->link = current;
if ( previous == NULL) {//用于检测新值是否应该添加到链表的起始位置
*rootp = new;
}else {
previous->link = new;
} return TRUE;
}

终极优化

把一个节点插入到链表的起始位置必须作为特殊情况进行处理吗?毕竟我们此时插入新节点需要修改的指针是根指针,对于任何其他节点,对指针进行修改时实际修改的是前一个节点的link 字段. 这两个看上去不同的操作实际上是一样的.

消除特殊情况的关键在于:链表中的每个节点都有一个指向它的指针. 对于第一个节点,这个指针是根指针,对于其他节点,这个指针是前一个节点的link字段.重点在于每个节点都有一个指针指向它.至于指针是否位于某个节点之内是无关紧要的.

取得当前节点内部的link字段的地址也是很方便的: &current->link

#include <stdlib.h>
#include <stdio.h>
#include "ssl_node.h" #define FALSE 0
#define TRUE 1
/**
把头指针和节点内指针同样对待,就不用对插入第一个节点之前的情况特殊对待了 @param linkp 指向头指针的指针
@param new_value 新值
@return 插入结构 1成功,0失败
*/
int ssl_insert_1( register Node **linkp, int new_value){
register Node *current;
register Node *new; //寻找正确的插入位置,方法是按序访问链表,直道到达一个其值大于或等于新值的节点
while ( (current = *linkp) != NULL && current->value < new_value) {
linkp = &current->link;
} //为新节点分配内存,并把新值存储到新节点中,如果内存分配失败,函数返回FALSE
new = (Node *)malloc( sizeof( Node) );
if (new == NULL) {
return FALSE;
}
new->value = new_value; //在链表中插入新节点
new->link = current;
*linkp = new;
return TRUE
}

C和C指针小记(十七)-使用结构和指针-链表的更多相关文章

  1. C和C指针小记(十五)-结构和联合

    1.结构 1.1 结构声明 在声明结构时,必须列出它包含的所有成员.这个列表包括每个成员的类型和名称. struct tag {member-list} variable-list; 例如 //A s ...

  2. 结构体指针,C语言结构体指针详解

    结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针. 指向结构体变量的指针 前面我们通过“结构体变量名.成员名”的方式引用结构体变量中的成员,除了这种方法之外还可以使用指针. 前面讲过,& ...

  3. C和C指针小记(六)-基本声明、指针声明、typedef 、常量、作用域、链接属性、存储类型、static

    1.变量的声明 声明变量的基本形式: 说明符号(一个或者多个) 声明表达式列表 说明符 (specifier) 包含一些关键字,用于描述被声明的标识符的基本类型,它也可用户改变标识符的缺省存储类型和作 ...

  4. C语言--- 高级指针2(结构体指针,数组作为函数参数)

    一.结构体指针 1. 什么是结构体指针?指向结构体变量的指针     结构体:     typedef  struct stu{                          char name[ ...

  5. Leetcode 2. Add Two Numbers(指针和new的使用)结构体指针

    ---恢复内容开始--- You are given two non-empty linked lists representing two non-negative integers. The di ...

  6. C与指针(结构体指针,函数指针,数组指针,指针数组)定义与使用

    类型 普通指针 指针数组(非指针类型) 数组指针 结构体指针 函数指针 二重指针 定义方式 int *p; int *p[5]; int (*p)[5]; int a[3][5]; struct{.. ...

  7. ctypes 操作 python 与 c++ dll 互传结构体指针

    CMakeLists.txt # project(工程名) project(blog-3123958139-1) # add_library(链接库名称 SHARED 链接库代码) add_libra ...

  8. python 传递结构体指针到 c++ dll

    CMakeLists.txt # project(工程名) project(xxx) # add_library(链接库名称 SHARED 链接库代码) add_library(xxx SHARED ...

  9. 【C语言入门教程】7.3 结构体指针的定义和引用

    C 语言中指针的操作非常灵活,它也能指向结构体变量对结构体变量进行操作.在学习结构指针之前,需要再次加深对指针的认识.声明指针变量时所使用的数据类型修饰符实际上的作用是定义指针访问内存的范围,如果指针 ...

随机推荐

  1. UVA524 素数环 Prime Ring Problem

    题目OJ地址: https://www.luogu.org/problemnew/show/UVA524 hdu oj 1016:  https://vjudge.net/problem/HDU-10 ...

  2. 使用 GeoIP2 获取 IP 的地理位置

    1. 准备工作 数据库 : 解析 IP 地理位置的的数据库来自 GeoLite2 开源数据库:https://dev.maxmind.com/geoip/geoip2/geolite2/ . C 语言 ...

  3. spring拦截器中使用spring的自动注入

    需要在spring的拦截器中使用自定义的服务,这要就设计到将服务注入到拦截器中.网上看的情况有两种: 1. @Configuration public class OptPermissionHandl ...

  4. python 验证码识别示例(一) 某个网站验证码识别

    某个招聘网站的验证码识别,过程如下 一: 原始验证码: 二: 首先对验证码进行分析,该验证码的数字颜色有变化,这个就是识别这个验证码遇到的比较难的问题,解决方法是使用PIL 中的  getpixel  ...

  5. Web API中如何获取相对地址的绝对地址 Server.MapPath

    var sPath = System.Web.Hosting.HostingEnvironment.MapPath("/FilePath/");

  6. Fluent动网格【12】:扩散光顺

    扩散光顺是Fluent提供的另外一种常用的网格光顺方法.其基本原理是通过求解扩散方程得到网格节点的运动位移. 扩散光顺基本计算 扩散光顺通过求解 以下扩散方程来设置网格的节点位置. \[ \nabla ...

  7. k8s官网 基础知识入门教程

    官网链接为 https://kubernetes.io/docs/tutorials/kubernetes-basics/ 基础操作环境为minikube 常见基础命令 查看基础的一些信息 # 查看版 ...

  8. JVM系列——从菜鸟到入门

    持续更新系列. 参考自<深入理解Java虚拟机>.<Java性能权威指南>.<分布式Java应用基础与实践>. Java的内存结构 JVM系列——运行时数据区 JV ...

  9. Scratch 可能能帮你找到学习编程的初心

    Scratch 是MIT 出品的一款少儿编程软件,基于Adobe Air开发,这个运行环境在如今已经显得有些过时,但只要这个软件有用,软件本身就不会过时. 编程的本质大致是调用计算机的指令编写一系列任 ...

  10. MPU6050带字符驱动的i2c从设备驱动1

    开干: 1.闲言碎语 这个驱动,越写觉的越简单,入门难,入门之后感觉还好.Linux开发还是比较友好的. 2.编写MPU6050带字符驱动的i2c从设备驱动 要实现的功能就是,将MPU6050作为字符 ...