首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >用只含一个链域的节点实现循环链表的双向遍历

用只含一个链域的节点实现循环链表的双向遍历

作者头像
llhthinker
发布于 2018-01-24 08:35:29
发布于 2018-01-24 08:35:29
89800
代码可运行
举报
运行总次数:0
代码可运行

通常来说,要实现循环双向链表,每个节点需要有两个链域:前驱和后继。现在的问题是:如何设计一种环形表,使表的每个结点只包含一个链域而又能够有效地对其进行两个方向的查找。本文将给出一种实现方式。

首先,在给出之前,需要先了解一种有趣的运算,那就是异或运算。异或运算的真值表如下:

A

B

A^B

0

0

0

0

1

1

1

0

1

1

1

0

通过异或的性质可以知道,对于任意一个二进制数a,有a^a = 0。利用这一性质,考虑下面一个经典例子:实现两个整数的交换

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void swap(int *x, int *y)
{
    *y = *x ^ *y;    /* step 1 */
    *x = *x ^ *y;    /* step 2 */
    *y = *x ^ *y;    /* step 3 */
}

为什么上述代码可以实现两个数的交换?以调用swap(&a, &b)为例,下表给出了解释:

step

*x

*y

Initialization

a

b

step 1

a

a^b

step 2

a^a^b=0^b=b

a^b

step 3

b

b^a^b=0^a=a

是的,通过上表可以知道,利用a^a = 0,我们可以这样“高大上”的实现两个数的交换(事实上,这种交换方式并没有性能上的优势)。但是我们可以借助它来理解异或运算。通过上表我们更加可以总结出公式a^a^b = b。这对于本文要解决的问题有什么启示呢?

要使得表的每个结点只包含一个链域而又能够有效地对其进行两个方向的查找,可以让节点的链域存结点的前驱prev和后继next的异或,再利用异或运算的性质,可以得到(prev ^ next) ^ next = prev; (prev ^ next) ^ prev = next 。我们可以把异或的链域看成一把特殊的锁,它有两把不同的钥匙,用钥匙next就可以打开前驱prev的门,而用钥匙prev就可以打开后继next的门。

环形表的设计如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct Node *Position;
typedef Position RingList;
struct Node
{
    int data;
    Position prevXORnext;        //前驱和后继的异或
};

在创建环形链表时,首先建立一个头节点rL,并申明节点指针prev和next,为了让头节点的链域可以直接指向第一个节点firstP,将prev初始化为0,由于0和某值的异或不会改变该值,故rL->prevXORnext = prev^next = 0^next。不过注意Position型不能直接做异或运算,需要强转为long long 型算出结果后再强转为Position型。(由于Position为结构体指针,指针的内存大小在32位机上为4个字节即32位,在64位机上是8个字节,所以为了通用性,将其转化为long long而不是int)

    创建环形链表函数如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RingList CreateRingList(Position *prevNode, Position *nextNode)
{
    RingList rL = new Node; //头结点
    int x;
    Position p = rL;
    Position prev = 0;   //初始头节点的异或就相当于next
    Position next;
    Position firstP = NULL, secondP = NULL;
    int n = 0;
    while (scanf("%d", &x) != EOF)
    {
        n++;
        Position newP = new Node;
        if (n == 1)
            firstP = newP;
        if (n == 2)
            secondP = newP;   //保存第二个节点的指针便于之后更新第一个节点
        newP->data = x;
        next = newP;
        p->prevXORnext = (Position)((LL)prev ^ (LL)next);  //LL为long long
        prev = p;
        p = newP;
    }
    //将尾指针的链域与第一个节点相关联
    p->prevXORnext = (Position)((LL)prev ^ (LL)firstP);
    //形成环后,更新第一个节点的链域
    firstP->prevXORnext = (Position)((LL)p ^ (LL)secondP);
    *prevNode = p;
    *nextNode = secondP;
    return rL;
}

如果我们要查找p结点的后继,只需要在之前临时保存p结点的前驱prev,然后令p = p->prevXORnext^prev,根据异或运算的性质可知当前p即为之前p的后继next。同理,如果要查找p结点的前驱,只需要在之前临时保存p结点的后继next,然后令p = p->prevXORnext^next,此时p即为之前p的前驱prev。

    顺时针访问函数如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void ClockWise(RingList rL, Position prev2)
{
    Position firstP = rL->prevXORnext;
    Position prev1 = firstP;
    printf("%d", firstP->data);
    Position cur = (Position)((LL)prev1->prevXORnext ^ (LL)(prev2));
    while (cur != firstP)
    {
        printf(" %d", cur->data);
        prev2 = prev1;
        prev1 = cur;
        cur = (Position)((LL)prev1->prevXORnext ^ (LL)(prev2));
    }
    printf("\n");
}

     逆时针访问函数如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void AntiClockWise(RingList rL, Position next2)
{
    Position firstP = rL->prevXORnext;
    Position next1 = firstP;
    printf("%d", firstP->data);
    Position cur = (Position)((LL)next1->prevXORnext ^ (LL)(next2));
    while (cur != firstP)
    {
        printf(" %d", cur->data);
        next2 = next1;
        next1 = cur;
        cur = (Position)((LL)next1->prevXORnext ^ (LL)(next2));
    }
    printf("\n");
}

下面是完整代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <cstdio>

using namespace std;

typedef long long LL;
typedef struct Node *Position;
typedef Position RingList;
struct Node
{
    int data;
    Position prevXORnext;
};

void ClockWise(RingList rL, Position prev2);             //顺时针
void AntiClockWise(RingList rL, Position next);          //逆时针
RingList CreateRingList(Position *prev, Position *next); //创建环形链表

int main()
{
    Position prev, next;
    RingList rL = CreateRingList(&prev, &next);

    printf("顺时针:");
    ClockWise(rL, prev);
    printf("逆时针:");
    AntiClockWise(rL, next);

    return 0;
}

RingList CreateRingList(Position *prevNode, Position *nextNode)
{
    RingList rL = new Node; //头结点
    int x;
    Position p = rL;
    Position prev = 0;   //初始头节点的异或就相当于next,而0异或某个数等于它本身
    Position next;
    Position firstP = NULL, secondP = NULL;
    int n = 0;
    while (scanf("%d", &x) != EOF)
    {
        n++;
        Position newP = new Node;
        if (n == 1)
            firstP = newP;
        if (n == 2)
            secondP = newP;     //保存第二个节点的指针便于之后更新第一个节点
        newP->data = x;
        next = newP;
        p->prevXORnext = (Position)((LL)prev ^ (LL)next);
        prev = p;
        p = newP;
    }
    //将尾指针的链域与第一个节点相关联
    p->prevXORnext = (Position)((LL)prev ^ (LL)firstP);
    //形成环后,更新第一个节点的链域
    firstP->prevXORnext = (Position)((LL)p ^ (LL)secondP);
    *prevNode = p;
    *nextNode = secondP;
    return rL;
}
void ClockWise(RingList rL, Position prev2)
{
    Position firstP = rL->prevXORnext;
    Position prev1 = firstP;
    printf("%d", firstP->data);
    Position cur = (Position)((LL)prev1->prevXORnext ^ (LL)(prev2));
    while (cur != firstP)
    {
        printf(" %d", cur->data);
        prev2 = prev1;
        prev1 = cur;
        cur = (Position)((LL)prev1->prevXORnext ^ (LL)(prev2));
    }
    printf("\n");
}

void AntiClockWise(RingList rL, Position next2)
{
    Position firstP = rL->prevXORnext;
    Position next1 = firstP;
    printf("%d", firstP->data);
    Position cur = (Position)((LL)next1->prevXORnext ^ (LL)(next2));
    while (cur != firstP)
    {
        printf(" %d", cur->data);
        next2 = next1;
        next1 = cur;
        cur = (Position)((LL)next1->prevXORnext ^ (LL)(next2));
    }
    printf("\n");
}

运行结果如下:

参考资料:《深入理解计算机系统》

(题外话:今天貌似是一个自发的程序员节:1024,虽然自己还是一个准程序员,也要祝自己节日快乐~hh~。希望在变成真正程序员之前这个节日能真正确定下来(●'◡'●))

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-10-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Linux开发利器:探秘开源,构建高效——基础开发工具指南(上)【包管理器/Vim】
♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个人主页✨✨✨✨✨✨
用户11352420
2025/07/24
1070
Linux开发利器:探秘开源,构建高效——基础开发工具指南(上)【包管理器/Vim】
Vim第七讲 帮助、个性化和补全
Vim 拥有一个细致全面的在线帮助系统。要启动该帮助系统,请选择如下三种方 法之一:
宋天伦
2020/07/15
6120
Vim 学习
本文的内容来自 vimtutor(v1.7),在 Unix 系统下输入 “vimtutor” 即可进入教学模型。这里记录下来学习点滴,方便以后查看。
李振
2021/11/26
7230
vim 从嫌弃到依赖(12)——打开及保存文件
在前几篇文章中,我们从vim各种模式的使用着手介绍了vim如何进行文本本身的编辑。也通过缓冲区列表的介绍了解到了vim是如何进行打开文件的管理。这篇我们将会着眼于文件的打开和保存的基本操作。通过这篇的阅读,我们可以正式开始尝试将vim用做代码编辑器,而不再是像之前那样作为普通的文本编辑器。
Masimaro
2022/05/19
2.1K0
vim 从嫌弃到依赖(12)——打开及保存文件
Vim 常用操作命令整理
打开关闭 vim xxx,vim +num xxx 命令行打开文件 vim + filename 启动跳到文件结尾 vim +143 filename 打开跳到143行 调试代码有用 vim +/search-term filename 跳到第一个匹配 vim +/search-term filename 跳到最后一个匹配 vim -t tag vim —cmd command filename 加载文件前执行命令 vim -c “:50” filename 加载文件后执行命令 :e xxx vim中打开文
哲洛不闹
2018/09/14
1K0
Vim第一讲 基础操作
特别提示:按下 <ESC> 键会带您回到正常模式或者撤消一个不想输入或部分完整 的命令。
宋天伦
2020/07/15
4080
vim配置即.vimrc文件的配置及vim操作技巧
1.下载vim(略)。让vi命令也可以使用vim的配置,需要修改 vi /etc/bashrc 增加如下一行内容
Twcat_tree
2022/11/30
4.5K0
Vim命令使用说明
vim是我最喜欢的编辑器,也是linux下第二强大的编辑器。 虽然emacs是公认的世界第一,我认为使用emacs并没有使用vi进行编辑来得高效。 如果是初学vi,运行一下vimtutor是个聪明的决定。 (如果你的系统环境不是中文,而你想使用中文的vimtutor,就运行vimtutor zh)
mikelLam
2022/10/31
3.3K0
2023最全vim编辑器教程(详细、完整)-编辑器之神
vi和vim是两款常用的文本编辑器。vi是Unix系统中最早的文本编辑器之一,vim是vi的改进版本。
Python兴趣圈
2023/11/10
3.8K0
2023最全vim编辑器教程(详细、完整)-编辑器之神
Vimtutor中文版
=============================================================================== = 欢 迎 阅 读 《 V I M 教 程 》 —— 版本 1.5 = =============================================================================== vim 是一个具有很多命令的功能非常强大的编辑器。限于篇幅,在本教程当中 就不详细介绍了。本教程的设计目标是讲述一些必要的基本命令,而掌握好这 些命令,您就能够很容易将vim当作一个通用的万能编辑器来使用了。
大数据流动
2019/08/08
1.7K0
【Python Learning第一篇】Linux命令学习及Vim命令的使用
学了两天,终于把基本命令学完了,掌握以后可以当半个程序员了♪(^∇^*) 此文是一篇备忘录或者查询笔记,如果哪位大佬看上了并且非常嫌弃的话,还请大佬不吝赐教,多多包涵 以下是我上课做的一些笔记,非常的凌乱,(⊙﹏⊙)反正是留给自己看的 Day1学习: 以Ubuntu为例子 Ctrl + Shift +‘+’ 变大 Ctrl + ‘-’变小 ls 能显示当前路径下的所有文件名及文件夹名的命令 Ubuntu没有盘符的概念,只有一个根目录 bin 放的是程序相关的 boot 和Ubuntu的启动项相关,开机项相关
Angel_Kitty
2018/04/08
1.1K0
vim 退出命令(保存、放弃保存)_linux保存并退出vim
今天第一次接触这个vim文本编辑器,拿到一个陌生的工具,我们想的当然是最短的时间掌握它的基本操作,体会到成就感。如果你跟我一样,那么这篇教程或许对你有所帮助。
全栈程序员站长
2022/09/23
21.7K0
vim从安装到熟练,这篇文章就够了
一简单介绍一下 下载分享的文件 链接: https://pan.baidu.com/s/1t8yS9jzjewSiGiawBEKcIg?pwd=y4wz 提取码: y4wz  压缩包里面有两个文件,一
sinnoo
2022/12/02
5K0
vim从安装到熟练,这篇文章就够了
vim-神之编辑器-命令汇总笔记
能够手不离键盘快速的书写,代码,文件等,但是要练熟了才能形成战斗力,否则几乎寸步难行。。
十四君
2019/11/25
1.2K0
一篇就学会vim
学会一个软技能,总结一篇文章就够了。 剩下要做的就是不停的练习,不停的尝试,本文是在学习这个仓库之后的极简总结中。 主要作为一个备忘录使用。
六个周
2022/10/28
3.6K0
一篇就学会vim
Linux学习笔记之vim操作指令大全
Vim是款强大的文本编辑器,但是众多指令需要学习,这次记录了指令大全方便以后翻阅。
Jetpropelledsnake21
2019/07/01
3.6K0
Linux学习笔记之vim操作指令大全
【Vim 核心攻略】 —— 文本编辑高手的进阶秘籍
Vim 是一种强大且高度可定制的文本编辑器,广泛用于软件开发、系统管理和各种文本处理任务。它基于更早期的编辑器 Vi,并对其进行了扩展,因此也被称为 “Vi Improved”(Vi 的增强版)。Vim 的特点是快捷键驱动、支持多模式编辑以及可扩展性强。
换一颗红豆
2024/12/23
4280
【Vim 核心攻略】 —— 文本编辑高手的进阶秘籍
vim 个性化设置
Vimscript,一门用于定制Vim的脚本语言。它其实就是 Vim命令。如,在Vim中,保存一个文件使用命令:write(或者缩写 :w)并回车确认。在Vimscript中,使用write实现文件保存功能。
PedroQin
2019/12/18
1.8K0
vim 个性化设置
终端文本编辑神器--Vim命令详解。如何配置Vim以及Vim插件?
Vim是一款跨平台的文本编辑器,不但可以运行在Unix,还可以运行在GNU、Windows平台,并且还支持丰富的插件,助力开发和使用。
Mintimate
2021/08/24
2.5K0
终端文本编辑神器--Vim命令详解。如何配置Vim以及Vim插件?
无插件Vim编程技巧
相信大家看过《简明Vim教程》也玩了《Vim大冒险》的游戏了,相信大家对Vim都有一个好的入门了。我在这里把我日常用Vim编程的一些技巧列出来给大家看看,希望对大家有用,另外,也是一个抛砖引玉的过程,也希望大家把你们的技巧跟贴一下,我会更新到这篇文章中。另外,这篇文章里的这些技巧全都是vim原生态的,不需要你安装什么插件。我的Vim的版本是7.2。
bear_fish
2018/09/19
1.5K0
无插件Vim编程技巧
推荐阅读
相关推荐
Linux开发利器:探秘开源,构建高效——基础开发工具指南(上)【包管理器/Vim】
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档