C和C指针小记(十七)-使用结构和指针-链表
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字段的地址也是很方便的: ¤t->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 = ¤t->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指针小记(十七)-使用结构和指针-链表的更多相关文章
- C和C指针小记(十五)-结构和联合
1.结构 1.1 结构声明 在声明结构时,必须列出它包含的所有成员.这个列表包括每个成员的类型和名称. struct tag {member-list} variable-list; 例如 //A s ...
- 结构体指针,C语言结构体指针详解
结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针. 指向结构体变量的指针 前面我们通过“结构体变量名.成员名”的方式引用结构体变量中的成员,除了这种方法之外还可以使用指针. 前面讲过,& ...
- C和C指针小记(六)-基本声明、指针声明、typedef 、常量、作用域、链接属性、存储类型、static
1.变量的声明 声明变量的基本形式: 说明符号(一个或者多个) 声明表达式列表 说明符 (specifier) 包含一些关键字,用于描述被声明的标识符的基本类型,它也可用户改变标识符的缺省存储类型和作 ...
- C语言--- 高级指针2(结构体指针,数组作为函数参数)
一.结构体指针 1. 什么是结构体指针?指向结构体变量的指针 结构体: typedef struct stu{ char name[ ...
- Leetcode 2. Add Two Numbers(指针和new的使用)结构体指针
---恢复内容开始--- You are given two non-empty linked lists representing two non-negative integers. The di ...
- C与指针(结构体指针,函数指针,数组指针,指针数组)定义与使用
类型 普通指针 指针数组(非指针类型) 数组指针 结构体指针 函数指针 二重指针 定义方式 int *p; int *p[5]; int (*p)[5]; int a[3][5]; struct{.. ...
- ctypes 操作 python 与 c++ dll 互传结构体指针
CMakeLists.txt # project(工程名) project(blog-3123958139-1) # add_library(链接库名称 SHARED 链接库代码) add_libra ...
- python 传递结构体指针到 c++ dll
CMakeLists.txt # project(工程名) project(xxx) # add_library(链接库名称 SHARED 链接库代码) add_library(xxx SHARED ...
- 【C语言入门教程】7.3 结构体指针的定义和引用
C 语言中指针的操作非常灵活,它也能指向结构体变量对结构体变量进行操作.在学习结构指针之前,需要再次加深对指针的认识.声明指针变量时所使用的数据类型修饰符实际上的作用是定义指针访问内存的范围,如果指针 ...
随机推荐
- springboot-admin自定义事件通知
springboot-admin组建已经提供了很多开箱即用的通知器(例如邮件),但在有些业务场景下我们需要做一些企业内部的通知渠道,这就需要我们来自定义通知器. 实现其实很简单,只需要往spring注 ...
- failed to create process怎么解决
python 在cmd时,报错:failed to create process怎么解决 在cmd命令前加 : python -m 命令(如:python -m conda update conda)
- BOOST 线程完全攻略
1 创建线程 首先看看boost::thread的构造函数吧,boost::thread有两个构造函数: (1)thread():构造一个表示当前执行线程的线程对象: (2)explicit thre ...
- Java之Servlet
Servlet规范了JavaWeb项目的结构Servlet的规范约束了服务器如何来实现Servlet规范,如何解析JavaWeb项目的结构. Java就是通过接口来约束 Servlet规范的jar就在 ...
- IntelliJIdea 2016.2 使用 tomcat 8.5 调试spring的web项目时,bean被实例化两次导致timer和thread被启动了两遍的问题的解决
今天新搭建了一个spring的web项目,项目启动时会启动一个线程,线程里定时执行任务,另外还启动了一个定时器,每秒钟统计系统吞吐量等业务性能数据.但是调试的时候惊奇的发现定时器和线程均被启动了两次. ...
- [lvs]lvs的三种模式
回顾了下lvs的三种模式的调度机制 1.lvs的dr模式中的arp的抑制,eth用自己口arp回应. 2.keepalive是否直接操作rs? 不直接操作, 只操作dr(配lvs) 3.tunnel模 ...
- 华为AR配置内部服务器示例(只有1个公网IP)
AR配置公网和私网用户都可以通过公网地址访问内部服务器示例(只有1个公网IP) 适用于:V200R003C01及以后的系统软件版本. 组网需求: 由于只有1个公网IP(100.100.1.2),想实现 ...
- Vue自用axios封装
[本文出自天外归云的博客园] 这是我的Vue项目中的request.js文件,请求报错了看console就会有具体请求信息,方便调试.分享一下. 其中用到了axios和element-ui的组件,ax ...
- 北京Java笔试题整理
北京Java笔试题整理 1.什么是java虚拟机?为什么ava被称作是"平台无关的编程语言? 答:Java虚拟机可以理解为一个特殊的"操作系统",只是它连接的不是硬件,而 ...
- ORACLE拼日期
Oracle数据库拼字符串是用"||"连接的.在开发中,经常会用到时间范围的查询 例如 startTime >='2017-05-22 00:00:00' and endT ...