当前位置: 首页 > news >正文

数据结构——二叉树线索化遍历(前中后序遍历)

二叉树线索化

线索化概念:

为什么要转换为线索化

        二叉树线索化是一种将普通二叉树转换为具有特殊线索(指向前驱和后继节点)的二叉树的过程。这种线索化的目的是为了提高对二叉树的遍历效率,特别是在不使用递归或栈的情况下进行遍历。

        将二叉树线索化的主要目的是为了提高对二叉树的遍历效率以及节省存储空间。线索化使得在不使用递归或栈的情况下可以更快速地进行遍历,特别是在特定顺序的遍历时,如前序、中序或后序遍历。  

  1. 提高遍历效率:线索化后,可以在常量时间内找到节点的前驱和后继节点,从而实现更高效的遍历。这对于需要频繁遍历大型二叉树或需要在树的中间部分执行插入和删除操作时特别有用。

  2. 无需递归或栈:线索化的二叉树允许你在遍历时省去递归或栈的开销,因为你可以沿着线索直接访问节点的前驱和后继,从而降低了内存和时间复杂度。

  3. 节省存储空间:线索化可以用较少的额外存储空间来实现。通常,只需为每个节点添加一个或两个指针来存储线索信息,而不需要额外的数据结构(如堆栈)来辅助遍历。

  4. 支持双向遍历:线索化的二叉树可以支持双向遍历,即可以在给定节点的前向和后向方向上遍历树。这在某些应用中很有用,例如双向链表的操作。

  5. 节省计算资源:在某些特定的应用场景中,通过线索化可以避免重复计算,因为可以直接访问前驱和后继节点,而无需再次搜索或遍历

树的遍历

        先给定一棵树,然后对他进行每种遍历:

        前序遍历:

        前序遍历,就是先遍历根节点在遍历左子树,在遍历右子树;

        他的顺序就是:根节点->左子树->右子树

        根据递归先遍历了根节点,然后递归左子树,回溯后在进行遍历右子树的一个过程;

        例如上图开始前序遍历:

        遍历根节点50,然后递归左子树,34,现在把34看为根节点继续递归左子树,28,然后把28看作根节点,继续遍历左子树,19,然后把19看为根节点继续遍历,然后左子树为空,开始回溯,回溯到19,遍历右子树也为空,继续回溯到结点28,遍历右子树,31;然后通过这种思想一直进行遍历最总遍历完整棵子树;

        前序遍历结果:50,34,28,19,31,41,81,72,95

        中序遍历:

        顺序是:左子树->根节点->右子树

        其实在理解了前序遍历后,中序遍历也差不多的,刚才是先记录了根节点,现在开始,先一直递归遍历左子树,递归到没有左子树的时开始记录,比如上图递归到19,然后回溯,28,遍历右子树31,回溯34,遍历右子树41,回溯50,遍历右子树;然后最终的结果就是:

        19,28,31,34,41,50,72,81,95;

        中序遍历就是,找到子树的最左边的那个结点,然后回溯到它的父节点,然后遍历他父节点的右子树,然后到右子树中又去找它的最左的结点,这样一直经过这样的操作,最终完成中序遍历。

        后序遍历:

        顺序是:左子树->右子树->根节点

        先是左子树,那就先找到左子树中的最左边的结点,19,然后回溯,遍历右子树,然后再右子树中找最左边的结点,31,回溯28,然后在回溯;这样的一个过程;

        最终结果就是:

        19,31,28,41,34,72,95,81,50

        后序遍历,看着比中序和后序遍历难理解,其实只要懂得了递归回溯的那个过程,就思路回非常的清晰;然后大概的过程就是先遍历左子树,找到左子树中最左边的结点,回溯,然后遍历右子树,遍历右子树的过程也是先遍历右子树中的左子树,然后再进行遍历右子树的右子树,最后来遍历他们的根节点;

        3种遍历的代码实现:  


void pre_orderNode(Node *root) {前序if (!root) return ;printf("%d ", root->data);//先输出根节点pre_orderNode(root->lchild);//遍历左子树pre_orderNode(root->rchild);//遍历右子树return ;
}void in_orderNode(Node *root) {中序if (!root) return ;in_orderNode(root->lchild);//先遍历左子树printf("%d ", root->data);//在打印根节点的值in_orderNode(root->rchild);//在遍历右子树return ;
}void post_orderNode(Node *root) {后序if (!root) return ;post_orderNode(root->lchild);//先遍历左子树post_orderNode(root->rchild);//在遍历右子树printf("%d ", root->data);//最终打印根节点值return ;
}

        可以去理解一下代码,尝试在纸上或者脑子里执行以下代码,模拟运行以下;

二叉树前序线索化:

        

        如图上图一个简单的二叉树,现在是将这个二叉树进行前序线索化,那么前序遍历的顺序是根节点->左子树->右子树;

        那么根节点就应该在条线的头部,然后再去遍历左子树和右子树,那么这个数的前序遍历结果是50,34,79;

        

        那么就可以将二叉树样来看,50是34的前驱,79是34的后继,前驱就是在遍历在他前面的,后继就是在他后面遍历的;

        现在我们将每个结点的指向两个子孩子指针改变为指向他的前驱和后继:

        通过这样的转换,那这棵树就转换为一条双向链表了:

        其中会发下50和79的会右一颗子树指向NULL,那么就需要用一个变量来记录,他是否右前驱或者后继;

        就是这样的一个转换,也证明了概念种的节省空间,不用递归遍历,可以双向遍历,提高了遍历效率;

        下面是代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define NORMAL 0
#define THRENA 1 //这里左儿子记录前驱,右儿子记录后继
typedef struct Node {int val, ltag, rtag;//val结点的值,ltag记录是否有前驱,rtag记录是否有后继struct Node *lchild, *rchild;
} Node;Node *getNewNode(int val) {//获取新的结点Node *root = (Node *)malloc(sizeof(Node));root->lchild = root->rchild = NULL;root->rtag = root->ltag = NORMAL;//NORMAL表示还未有前驱或后继root->val = val;return root;
}Node *insert(Node *root, int val) {//添加结点,组成普通的二叉树if (!root) return getNewNode(val);if (root->val == val) return root;if (root->val > val) root->lchild = insert(root->lchild, val);else root->rchild = insert(root->rchild, val);return root;
}void build_Thread(Node *root) {//建立线索化if (!root) return ; static Node *pre = NULL;//使用静态变量使得pre值不随程序递归而改变Node *left = root->lchild;//记录当前结点左右儿子 Node *right = root->rchild;if (root->ltag == NORMAL) {//当前结点没有前驱结点root->lchild = pre;//给当前结点赋值前驱结点root->ltag = THRENA;//标记有前驱结点}if (pre && pre->rtag == NORMAL) {//如果它的前驱结点没有后继,并且前驱结点不为NULLpre->rchild = root;//将前驱结点的后继附上当前结点pre->rtag = THRENA;//标记前驱结点有后继了}pre = root;//pre等于当前递归的结点build_Thread(left);//递归左子树build_Thread(right);//在递归右子树return ;
}void output(Node *root) {//遍历线索化二叉树if (!root) return ;Node *p = root;while (p) {printf("%d ", p->val);if (p->rtag == THRENA) p = p->rchild;//说明当前结点有后继直接往右节点也就是后继结点继续遍历else if (p->rtag == NORMAL) break;//如果当前结点没有后继结束遍历 }return ;
}void preface(Node *root) {//普通前序递归遍历if (!root) return ;printf("%d ", root->val);if (root->ltag == NORMAL) preface(root->lchild);if (root->rtag == NORMAL) preface(root->rchild);return ;
}void clearNode(Node *root) {//回收空间if (!root) return ;if (root->ltag == NORMAL) clearNode(root->lchild);if (root->rtag == NORMAL) clearNode(root->rchild);free(root);return ;
}int main() {srand(time(0));//获取当期计算时间,获取随机数Node *root = NULL;for (int i = 0; i < 10; i++) {int val = rand() % 100;root = insert(root, val);}preface(root);//先打印普通前序遍历putchar(10);//换行build_Thread(root);//建立线索化output(root);//输出前序线索化putchar(10);clearNode(root);return 0;
}

二叉树中序线索化:

       

        中序遍历顺序:左子树->根节点->右子树

        那么结果借是34,50,79

        转换线索化,那么34就是50的前驱,50就是34的后继;和前序遍历的是一样的;

        直接来看代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NORMAL 0 //表示没有
#define THRENA 1 //表示有
//这里左儿子记录前驱,右儿子记录后继
typedef struct Node {int val, ltag, rtag;///val结点的值,ltag记录是否有前驱,rtag记录是否有后继struct Node *lchild, *rchild; 
} Node;Node *getNewNode(int val) {///获取新的结点Node *p = (Node *)malloc(sizeof(Node));p->val = val;p->lchild = p->rchild = NULL;p->ltag = p->rtag = NORMAL;//NORMAL表示还未有前驱或后继return p;
}Node *insert(Node *root, int val) {//添加结点,组成普通的二叉树if (!root) return getNewNode(val);if (root->val == val) return root;if (root->val > val) root->lchild = insert(root->lchild, val);else root->rchild = insert(root->rchild, val);return root;
}void in_order(Node *root) {//普通递归中序遍历if (!root) return ;if (root->ltag == NORMAL) in_order(root->lchild);printf("%d ", root->val);if (root->rtag == NORMAL) in_order(root->rchild);return ;
}void build_Thread(Node *root) {//建立线索化if (!root) return ;static Node *pre = NULL;//使用静态变量使得pre值不随函数递归过程改变而改变build_Thread(root->lchild);//由于是中序遍历,先递归到最左结点//中间过程就是想当于根节点if (root->ltag = NORMAL) {root->lchild = pre;root->ltag = THRENA;}if (pre && pre->rchild == NORMAL) {pre->rchild = root; pre->rtag = THRENA;}pre = root;build_Thread(root->rchild);//然后再遍历右子树return ;
}Node *most_left(Node *root) {//找到最左结点Node *temp = root;while (temp && temp->ltag == NORMAL && temp->lchild != NULL) temp = temp->lchild;return temp;
}void output(Node *root) {if (!root) ;Node *p = most_left(root);//从最左结点开始遍历while (p) {printf("%d ", p->val);if (p->rtag == THRENA) p = p->rchild;//如果后继存在进行遍历后继else p = most_left(p->rchild);//找到右子树的最左结点继续遍历}return ;
}void clear(Node *root) {if (!root) return ;if (root->ltag == NORMAL) clear(root->lchild);if (root->rtag == NORMAL) clear(root->rchild);free(root);return ;
}int main() {srand(time(0));    Node *root = NULL;for (int i = 0; i < 10; i++) {int val = rand() % 100;root = insert(root, val);}in_order(root);putchar(10);build_Thread(root);output(root);putchar(10);clear(root);return 0;
}

        二叉树后序线索化:

        直接上代码演示,如果前面两种你都弄懂了那么,后序理解起来也非常容易:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define NORMAL 0
#define THRENA 1 typedef struct Node {int val, ltag, rtag;struct Node *lchild, *rchild;
} Node;Node *getNewNode(int val) {Node *root = (Node *)malloc(sizeof(Node));root->lchild = root->rchild = NULL;root->rtag = root->ltag = NORMAL;root->val = val;return root;
}Node *insert(Node *root, int val) {if (!root) return getNewNode(val);if (root->val == val) return root;if (root->val > val) root->lchild = insert(root->lchild, val);else root->rchild = insert(root->rchild, val);return root;
}void build_Thread(Node *root) {if (!root) return ; static Node *pre = NULL;build_Thread(root->lchild);build_Thread(root->rchild);if (root->ltag == NORMAL) {root->lchild = pre;root->ltag = THRENA;}if (pre && root->rtag == NORMAL) {pre->rchild = root;pre->rtag = THRENA;}pre = root;return ;
}Node *most_left(Node *root) {while (root && root->ltag == THRENA && root->lchild) root = root->lchild;return root;
}void output(Node *root) {if (!root) return ;Node *p = most_left(root);while (p) {printf("%d ", p->val);if (p->rtag == THRENA) p = p->rchild;  else if (p->rtag == NORMAL) break;}return ;
}void back_order(Node *root) {if (!root) return ;if (root->ltag == NORMAL) back_order(root->lchild);if (root->rtag == NORMAL) back_order(root->rchild);printf("%d ", root->val);return ;
}void preface(Node *root) {if (!root) return ;printf("%d(", root->val);if (root->ltag == NORMAL) preface(root->lchild);printf(",");if (root->rtag == NORMAL) preface(root->rchild);printf(")");return ;
}void clearNode(Node *root) {if (!root) return ;if (root->ltag == NORMAL) clearNode(root->lchild);if (root->rtag == NORMAL) clearNode(root->rchild);free(root);return ;
}int main() {srand(time(0)); Node *root = NULL;for (int i = 0; i < 10; i++) {int val = rand() % 100;root = insert(root, val);}preface(root);putchar(10);back_order(root);putchar(10);build_Thread(root);output(root);putchar(10);clearNode(root);return 0;
}

相关文章:

数据结构——二叉树线索化遍历(前中后序遍历)

二叉树线索化 线索化概念&#xff1a; 为什么要转换为线索化 二叉树线索化是一种将普通二叉树转换为具有特殊线索&#xff08;指向前驱和后继节点&#xff09;的二叉树的过程。这种线索化的目的是为了提高对二叉树的遍历效率&#xff0c;特别是在不使用递归或栈的情况下进行遍历…...

GO语言网络编程(并发编程)Channel

GO语言网络编程&#xff08;并发编程&#xff09;Channel 1、Channel 1.1.1 Channel 单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。 虽然可以使用共享内存进行数据交换&#xff0c;但是共享内存在不同的goroutine中容易发生竞态…...

c++day3

stack.h #ifndef STACK_H #define STACK_H #include <iostream> //#define max 128 using namespace std; class Stack { private:int* stack;//数组指针int top;//栈顶元素int max;//栈容量 public://构造函数Stack();//析构函数~Stack();//定义拷贝构造函数Stack(cons…...

算法通过村第六关-树青铜笔记|中序后序

文章目录 前言1. 树的常见概念2. 树的性质3. 树的定义与存储方式4. 树的遍历方式5. 通过序列构建二叉树5.1 前中序列恢复二叉树5.2 中后序列恢复二叉树 总结 前言 提示&#xff1a;瑞秋是个小甜心&#xff0c;她只喜欢被爱&#xff0c;不懂的去爱人。 --几米《你们 我们 他们》…...

C++动态内存管理+模板

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; C&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大…...

SQL 注入漏洞攻击

文章目录 1. 介绍2. 无密码登录3. 无用户名无密码登录4. 合并表获取用户名密码 1. 介绍 假设你用自己的用户名和密码登录了一个付费网站&#xff0c;网站服务器就会查询一下你是不是 VIP 用户&#xff0c;而用户数据都是放在数据库中的&#xff0c;服务器通常都会向数据库进行查…...

一篇五分生信临床模型预测文章代码复现——Figure 10.机制及肿瘤免疫浸润(四)

之前讲过临床模型预测的专栏,但那只是基础版本,下面我们以自噬相关基因为例子,模仿一篇五分文章,将图和代码复现出来,学会本专栏课程,可以具备发一篇五分左右文章的水平: 本专栏目录如下: Figure 1:差异表达基因及预后基因筛选(图片仅供参考) Figure 2. 生存分析,…...

Transformer 模型中常见的特殊符号

Transformer 模型中常见的特殊符号 通过代码一起理解一下 Transformer 模型中常见的特殊符号&#xff0c; 示例代码&#xff0c; special_tokens{unk_token: [UNK], sep_token: [SEP], pad_token: [PAD], cls_token: [CLS], mask_token: [MASK]}这段代码是定义了一个字典spec…...

C# halcon SubImage的使用

SubImage(HObject imageMinuend, HObject imageSubtrahend, out HObject imageSub, HTuple mult, HTuple add) 公式 x1imageMinuend此行此列的灰度 x2imageSubtrahend此行此列的灰度 则imageSub此行此列的灰度为;(x1-x2)*multadd 溢出裁剪 以byte图为例&#xff0c;小于0&a…...

每天几道Java面试题:异常机制(第三天)

目录 第三幕、第一场&#xff09;异常机制面试题 友情提醒 背面试题很枯燥&#xff0c;加入一些戏剧场景故事人物来加深记忆。PS:点击文章目录可直接跳转到文章指定位置。 第三幕、 第一场&#xff09;异常机制面试题 【面试官老吉&#xff0c;面试官潘安&#xff0c;面试者…...

Linux 中的 chattr 命令及示例

Linux 中的chattr命令是一个文件系统命令,用于更改目录中文件的属性。该命令的主要用途是使多个文件无法被超级用户以外的用户更改。管理员表示,众所周知,Linux 是一个多用户操作系统,一个用户有可能删除另一个用户非常关心的文件。为了避免这种情况,Linux 提供了“ chatt…...

LeetCode 2605. Form Smallest Number From Two Digit Arrays【数组,哈希表,枚举;位运算】1241

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…...

VoxWeekly|The Sandbox 生态周报|20230904

欢迎来到由 The Sandbox 发布的《VoxWeekly》。我们会在每周发布&#xff0c;对上一周 The Sandbox 生态系统所发生的事情进行总结。 如果你喜欢我们内容&#xff0c;欢迎与朋友和家人分享。请订阅我们的 Medium 、关注我们的 Twitter&#xff0c;并加入 Discord 社区&#xf…...

antd setFieldsValue 设置初始值无效AutoComplete 设置默认值失败

antd form setFieldsValue 设置初始值无效 解决方案 setTimeout(()>{setFieldsValue(values)},100)antd AutoComplete 设置默认值失败 defaultValue 设置无效 解决方案 设置value&#xff0c;搭配onChange来设置修改...

01-Redis核心数据结构与高性能原理

上一篇&#xff1a; 1.Redis安装 下载地址&#xff1a;http://redis.io/download 安装步骤&#xff1a; # 安装gcc yum install gcc# 把下载好的redis-5.0.3.tar.gz放在/usr/local文件夹下&#xff0c;并解压 wget http://download.redis.io/releases/redis-5.0.3.tar.gz…...

预防Dos攻击

Dos----拒绝服务攻击&#xff0c;一般是构造特殊的输入&#xff0c;使得后台的处理耗时远超正常水平&#xff0c;随着请求越来越多&#xff0c;后台服务越发疲于奔命&#xff0c;最后因资源耗尽&#xff0c;无法再接受新的请求&#xff0c;最终造成拒绝服务的效果。 特殊输入例…...

ant design的文档真的是一坨屎

很多基础设置 高傲的写都不写 要自己去index.d.ts里查 这就算了&#xff0c;为什么还有错的。。。。。 即使因为版本号而不同&#xff0c;起码把差异说明一下吧&#xff0c;直接丢个错的什么意思&#xff0c;。。。。。。。。 没点子功夫还真用不了 文档 进度条 Progress -…...

关于迁移学习的一点理解

举个栗子&#xff0c;老虎图片的数量非常少&#xff0c;可以让网络先学会识别猫的图片 1、预训练模型 内容&#xff1a;利用在 ImageNet1000 数据集训练好的模型&#xff0c;将所需的模型参数下载&#xff0c;嵌入到对应的网络架构中&#xff0c;使用对预训练模型的搭建。目前P…...

【力扣周赛】第 361 场周赛(⭐前缀和+哈希表 树上倍增、LCA⭐)

文章目录 竞赛链接Q1&#xff1a;7020. 统计对称整数的数目竞赛时代码——枚举预处理 Q2&#xff1a;8040. 生成特殊数字的最少操作&#xff08;倒序遍历、贪心&#xff09;竞赛时代码——检查0、00、25、50、75 Q3&#xff1a;2845. 统计趣味子数组的数目竞赛时代码——前缀和…...

解决 Android 依赖冲突

解决办法 问题原因就是&#xff0c;各个模块所有的依赖&#xff08;递归&#xff09;的 jar 包最后都会加载到安卓的项目中&#xff0c;你可以选择 project 形式查看 External Libraries&#xff0c;都在这了。所以解决问题关键就是干掉冲突&#xff0c;剩下一个就行了&#xf…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

Mysql故障排插与环境优化

前置知识点 最上层是一些客户端和连接服务&#xff0c;包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念&#xff0c;为通过安全认证接入的客户端提供线程。同样在该层上可…...

负载均衡器》》LVS、Nginx、HAproxy 区别

虚拟主机 先4&#xff0c;后7...

数据分析六部曲?

引言 上一章我们说到了数据分析六部曲&#xff0c;何谓六部曲呢&#xff1f; 其实啊&#xff0c;数据分析没那么难&#xff0c;只要掌握了下面这六个步骤&#xff0c;也就是数据分析六部曲&#xff0c;就算你是个啥都不懂的小白&#xff0c;也能慢慢上手做数据分析啦。 第一…...