├── 1.排序篇之冒泡排序.md ├── 10.数据结构之二叉搜索树(二).md ├── 11.数据结构之栈和函数调用的二三事.md ├── 12.数据结构之最大堆和最小堆.md ├── 13.基础查找之二分查找.md ├── 14.排序篇之快速排序之入门篇.md ├── 15.基础查找之索引入门.md ├── 2.排序篇之选择排序.md ├── 3.排序篇之直接插入排序.md ├── 4.数据结构之线性表(顺序存储表).md ├── 5.数据结构之线性表(链式存储表).md ├── 6.数据结构之栈.md ├── 7.数据结构之队列.md ├── 8.数据结构之树的基本枯燥概念(一).md ├── 9.数据结构之树的代码实现(二).md ├── LICENSE ├── README.md ├── clang.binary-search.c ├── clang.binary-tree.c ├── clang.hash-table.c ├── clang.linear-list.c ├── clang.linear-search.for-version.c ├── clang.linear-search.while-version.c ├── clang.link-queue.c ├── clang.link-stack.c ├── clang.seq-cycle-queue.c ├── clang.seq-stack.c ├── clang.single-link-list.c └── clang.two-seq-stack-share-mem.c /1.排序篇之冒泡排序.md: -------------------------------------------------------------------------------- 1 | 排序,顾名思义,就是把一坨数字按照某种特定的顺序排列好了,比如从小到大又或者从大到小。 2 | 3 | 当然了,当王老师第一次把6,4,7,2,9,8,1七个数字写到黑板上并开始提问:“ 你们有什么办法让这一坨数字从小到大排列呢? ”,作为菜B,你的第一想法肯定是先把脑袋深深淹没在书堆后并默念:“ 千万别点老子 ”。 4 | 5 | ![](http://static.ti-node.com/6401076018118918144) 6 | 7 | 然而事情往往都是“ 你怕啥,就来啥 ”,硬着头皮站起来,不想一个法子看起来是别想坐下去了,而且,秋雅还在旁边看着呢。 8 | 9 | 作为菜B,上了多少年学,第一次遇到如此艰难的算数运算题,脑海中想动的也自然是最直接明了的办法了:“先拿第一个位置上的数字6跟后面的每一个数字都轮番比试大小,放到本题中呢,就是先跟4比大小,6比4大,那么6和4互换一下位置,然后由4继续和后面的数字进行比较,也就是4和7进行比大小,4比7小,所以没有任何操作,接着4和2比大小,4比2大,所以4和2交换位置,然后由2继续后面的数字比试,也就是2和9比大小,2比9小,所以没有任何操作,接着2比8小,没有任何操作,最后2和1比大小,2比1大,所以2和1交换位置。经过这一轮比较,我们可以保证最小的数字1已经放到第一位了,然后下面从第二位开始就可以了,重复上述动作即可”。 10 | 11 | 这个时候,不要偷懒,拿起你的纸和笔和我一样,手工走一下第一轮比试吧,会让你加深理解的!我给大家提供一下我的排序流程图: 12 | 13 | ![](http://static.ti-node.com/6401371261846421505) 14 | 15 | 当然了菜B也有菜B的尊严,既然想出来了,想必也是不容易的,用程序实现一把: 16 | ```php 17 | $arr[ $inner ] ){ 27 | $temp = $arr[ $outer ]; 28 | $arr[ $outer ] = $arr[ $inner ]; 29 | $arr[ $inner ] = $temp; 30 | } 31 | } 32 | } 33 | print_r( $arr ); 34 | ``` 35 | 运行一把代码,结果如下图所示: 36 | ![](http://static.ti-node.com/6401360546519580673) 37 | 38 | 简单总结一下要点: 39 | - 需要两次循环才可以完成,外面一层循环( outer ),里面再包一层循环( inner ) 40 | - 比大小的时候,如果前面的数字比后面的大,二者是要交换位置的,但是交换完位置后,原来的比试不能中断,还要继续,而且不能再拿原来的数字和后面的比了。比如6和4,6比4大,6和4交换位置,交换完毕后该轮比试还未完毕,但是却不能拿6和后面的比试了,应该拿4和后面的比试 41 | - 值得注意的是,第一轮比试完毕后,我们竟然很悲催地将2这个倒数第二小的数字弄到了最后,妈蛋,怎么感觉白费功夫 42 | 43 | 如果你觉得冒泡排序这就已经完成了,那王老师就只能呵呵了,本质上讲,上述办法更多倾向于是一种“交换排序”,并非冒泡,冒泡嘛,形象一点儿,是一个泡泡往上涌,然后和“相邻的泡泡”比试,最后最小的泡泡浮到了水面上。既然我们想要让小的从后排都涌到前排,那么为何不尝试一下最后向前呢? 44 | 45 | 这种情况下,当outer = 1的时候,我们从1开始,让1和8比,1比8小,互换位置,1和9比,以此类推,推演图如下(再次强烈建议大家亲自动手动笔画推演图,非常有助于理解,这也是我为什么不用画图软件的原因,希望能够带动围观者一起下笔亲自画): 46 | 47 | ![](http://static.ti-node.com/6401372328428568576) 48 | 49 | 当outer = 1这一轮排序完成后,最终序列是1,6,4,7,2,9,8。 50 | 而在上面第一版本的排序第一轮排序完成是1,6,7,4,9,8,2。 51 | 52 | 通过对比,我们可以觉察出来,这种算法有一种将小数字往前排,大数字往后排的趋势动力,但从这点上看,确实是要比上个版本进步了很多的。现在,我们通过程序来实现一把: 53 | ```php 54 | $outer; $inner-- ){ 62 | if( $arr[ $inner ] < $arr[ $inner - 1 ] ){ 63 | $temp = $arr[ $inner ]; 64 | $arr[ $inner ] = $arr[ $inner - 1 ]; 65 | $arr[ $inner - 1 ] = $temp; 66 | } 67 | } 68 | } 69 | print_r( $arr ); 70 | ``` 71 | 72 | 可以将outer外部循环每次完成后的数组打印出来,你可以观察整个数组中数字移动的趋势,整体看,是小的数字在就像泡泡一样往上浮起来,而大的数字则在往下沉,这才叫“冒泡”! 73 | 74 | 现在,我们假设一种极端的情况,比如给我们提供的数组是1,2,3,4,5,6,怎么办?你一看就知道,这不用排啊,是啊,王老师也一看就知道不用排了,但计算机可不知道啊!这种明明已经排好序的数组,在你的代码业务流程中依然还是要让程序白白跑一边的,在低碳环保的今天,王老师并不认为这是一种可取的做法,白了你一眼说:“应该极力降低这种损耗成本,动动脑子改改吧,改完后再提交到班级gitlab上”。 75 | 76 | 实际上,如果说这一坨数字本身就是从小到达顺序的,那么我们从代码上看就知道,它一定不会发生“交换位置”这一段流程,所以,在鼓励师秋雅给你一顿按摩吹捧后,你决定如此修改一下代码: 77 | ```php 78 | $arr = [ 1,2,3,4,5,6 ]; 79 | $length = count( $arr ); 80 | // 外部循环 81 | $swap = true; 82 | for( $outer = 0; $outer < $length && $swap; $outer++ ){ 83 | echo "outer : ".$outer.PHP_EOL; 84 | $swap = false; 85 | // 当外部循环开始第一轮的时候,从倒数第一位开始往前对比,一直到与正数第一位比较完后终止 86 | // 当外部循环开始第一轮的时候,从倒数第一位开始往前对比,一直到与正数第二位比较完后终止 87 | for( $inner = $length - 1; $inner > $outer; $inner-- ){ 88 | if( $arr[ $inner ] < $arr[ $inner - 1 ] ){ 89 | $temp = $arr[ $inner ]; 90 | $arr[ $inner ] = $arr[ $inner - 1 ]; 91 | $arr[ $inner - 1 ] = $temp; 92 | $swap = true; 93 | } 94 | } 95 | } 96 | print_r( $arr ); 97 | ``` 98 | 运行代码,我们可以看到程序只打印了一次outer,也就说程序只执行了一次outer大循环后,之后便不再浪费自己脑力体力了。 99 | 100 | 撒花,一个从无到有,从烂到好冒泡排序就此诞生,完结! 101 | 102 | ### 后感:第一次写算法,文笔组织不是特别好,也能明显感觉出来写算法要比写其他文章表达难度更高,很多东西用语言不太容易描述,可能动画会更好,但我还是希望各位亲自动手,上笔上纸,亲自操刀演练,方能理解深刻。 103 | 104 | ------ 105 | -------------------------------------------------------------------------------- /10.数据结构之二叉搜索树(二).md: -------------------------------------------------------------------------------- 1 | 前面讲了一坨跟树有关的玩意,无论是理解搞定了,还是死记硬背搞定的,完事后都会如下图所示: 2 | 3 | ![](http://static.ti-node.com/6405980790953345025) 4 | 5 | 为了表示二叉树是有一些卵用的,所以今天引入二叉搜索树,近义词是二叉查找树或者二叉排序树,是一种常见并常用的数据结构。二叉搜索树整体是有序的,比如从大到小又或者从小到大,正是因为有序,所以才会利于查找。举个例子:一坨数字15、10、18、7、12、16、19,让你快速从中找到16。作为蠢货,我的第一个想法就是循环,挨个对比,啥时候对上号了,那就算找到了,然后我运算6次,找到了16。 6 | 7 | 然而,一些稍微不傻的人会考虑从中间7开始对比,这个时候会有两种选择:从7往前,从7往后。从7往前的就比较悲催了,但是从7往后的,3次就能找到16。 8 | 9 | 最后,一些聪明人就开始基于“稍微不傻”的这些人的想法继续想:“要是选中间数的时候,能够知道要找数字在中间数前面或者后面就好了,一次可以锁定一个区域!”,于是搜索二叉树应运而生,如下图: 10 | 11 | ![](http://static.ti-node.com/6405996854143614977) 12 | 13 | 如果我们要找19,19比15大,那一定是在15的右子树上,接着15的右子树的根节点就是18,19依然比18大,然后向18的右子树进发,bingo,找到了。 14 | 15 | ### 数据量越大,这种二叉搜索树越是能体现出自己的优势! 16 | 17 | 通过上面的俗话和示例似乎并不能准确描述二叉搜索树(二叉排序树、二叉查找树),有时候官方语言还是要套一套的: 18 | - 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值 19 | - 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值 20 | - 任意节点的左、右子树也分别为二叉查找树 21 | - 没有键值相等的节点 22 | 23 | 二叉搜索树首先是一颗二叉树,其次是带“搜索”优化功能,所以,我们将前几节中的代码直接复制粘贴然后魔改一把,你们感受一下: 24 | 25 | ```php 26 | data = $value; 38 | } 39 | // 添加左子树 40 | public function setLeft( Node $node ){ 41 | // 将当前class实例化后的结点作为父结点 42 | $node->parent = $this; 43 | $this->left = $node; 44 | return $node; 45 | } 46 | // 添加右子树 47 | public function setRight( Node $node ){ 48 | // 将当前class实例化后的结点作为父结点 49 | $node->parent = $this; 50 | $this->right = $node; 51 | return $node; 52 | } 53 | // 前序遍历 54 | public function preOrder( $tree ){ 55 | if( $tree instanceof Node ){ 56 | // 先遍历根节点 57 | echo $tree->data.' '; 58 | // 然后遍历左子树 59 | $this->preOrder( $tree->left ); 60 | // 最后遍历右子树 61 | $this->preOrder( $tree->right ); 62 | } 63 | } 64 | // 中序遍历 65 | public function midOrder( $tree ){ 66 | if( $tree instanceof Node ){ 67 | // 先遍历左子树 68 | $this->midOrder( $tree->left ); 69 | // 再遍历根节点 70 | echo $tree->data.' '; 71 | // 后遍历右子树 72 | $this->midOrder( $tree->right ); 73 | } 74 | } 75 | // 后续遍历 76 | public function sufOrder( $tree ){ 77 | if( $tree instanceof Node ){ 78 | // 先遍历左子树 79 | $this->sufOrder( $tree->left ); 80 | // 再遍历右子树 81 | $this->sufOrder( $tree->right ); 82 | // 最后遍历根节点 83 | echo $tree->data.' '; 84 | } 85 | } 86 | } 87 | ``` 88 | 上面代码就是上节课完整的代码,但是,很明显由于二叉搜索树的特征,setLeft和setRight两个方法算是报废了,最起码说,是不能直接调用的。迫于无奈,不得不分析一波儿了:拿新结点的数值和原有树的根节点开始比较,比根节点大,那么往右走;比根节点小,那么往左走。然后我们将根节点的左子树或者右子树依然当作一颗独立完整的树来看待,那么,然后继续用新结点与之进行上述步骤。。。是不是闻到了一股浓重的递归味儿? 89 | 90 | ```php 91 | data < $tree->data ){ 96 | if( $tree->left ){ 97 | $tree = $tree->left; 98 | self::insert( $tree, $node ); 99 | } 100 | // 如果不存在左子树 101 | else{ 102 | $tree->setLeft( $node ); 103 | } 104 | } 105 | // 新结点值比当前结点大,往右走 106 | else if( $node->data > $tree->data ){ 107 | if( $tree->right ){ 108 | $tree = $tree->right; 109 | self::insert( $tree, $node ); 110 | } 111 | // 如果不存在左子树 112 | else{ 113 | $tree->setRight( $node ); 114 | } 115 | } 116 | } 117 | ``` 118 | 119 | 开篇吹过了,搜索二叉树一大优势就是快速查找确定数值的结点,那么是时候出来溜溜了。其实查找本身也是也是根据搜索二叉树的特征进行查找的,整个过程和添加结点是颇为相似的。依然是从根节点开始,要么find的目标结点比根节点的小,要么find的目标结点比根结点的大,小了往左走,大了往右走。无论是往左走还是往右走,我们都可以把其子树当作独立的一个树,和要find的目标依然重复上述操作,最终当要find的目标既不大于根结点也不小于根节点,也就是意味着找到该结点了。同时,我们知道一颗搜索二叉树的最小值一定是最左下角那个结点,最大值一定是最右下角那个结点,所以这两个方法应该很容易写出来,你们感受一下: 120 | 121 | ```php 122 | data ){ 129 | $tree = $tree->left; 130 | }else if( $value > $tree->data ){ 131 | $tree = $tree->right; 132 | } else { 133 | return $tree; 134 | } 135 | } 136 | } 137 | // 最小的 一定就是最左下角位置 138 | public static function findMin( $tree ){ 139 | while( $tree->left ){ 140 | $tree = $tree->left; 141 | } 142 | return $tree->data; 143 | } 144 | // 最大的 一定就是最右下角位置 145 | public static function findMax( $tree ){ 146 | while( $tree->right ){ 147 | $tree = $tree->right; 148 | } 149 | return $tree->data; 150 | } 151 | ``` 152 | 码完代码,跑一个简单的小测试。这里值得注意的一点是,如果对一个搜索二叉树进行中序遍历,那么会得到一个有序数字队列。 153 | 154 | ```php 155 | preOrder( $tree ).PHP_EOL; 173 | echo $tree->midOrder( $tree ).PHP_EOL; 174 | //echo $tree->sufOrder( $tree ).PHP_EOL; 175 | ``` 176 | -------------------------------------------------------------------------------- /11.数据结构之栈和函数调用的二三事.md: -------------------------------------------------------------------------------- 1 | 写了一坨关于树的一些内容,其中对于树的某些操作,大量地运用了递归操作,那么,也是时候回来看一下递归了。 2 | 3 | 简单说来,一般我们通过“一个函数自己调用自己”来给递归下定义,其实这个还是比较难理解的:“妈蛋怎么能自己调用自己呢?你自己都还没定义完呢... ...什么玩意”。当然了,作为众多蠢货中的普通一员,即便我们不理解也没关系,死记硬背过又不是不行。 4 | 5 | 不过,作为PHP文化传播圣地,怎么能让你们白来一趟?同时我自己还能装一波儿13,两全其美、一石二鸟、一举两得。 6 | 7 | 有为青年看到标题后就已经在隐隐之间感受到了一些东西。本质上讲,递归就是“函数自己调用自己”,实际上这句话也就说递归就是一种函数调用而已。明人不装暗逼:函数调用利用的就是栈数据结构!没想到,作为CURDer混了这么多年,码了一坨函数调用来调用去,原来一直靠的是人家栈混饭吃。所以,掌握好数据结构什么的,那想必是... ... 8 | 9 | “你记不记得有一招从天而降的掌法?” 10 | 11 | ![](http://static.ti-node.com/6411222699867111425) 12 | 13 | 首先,我们从普通的函数调用开始,看下调用过程中栈究竟起到了什么作用,比如下面这坨代码: 14 | 15 | ```php 16 | element[ 0 ] = null; 34 | $this->fn = function( $data ){ 35 | return $data; 36 | }; 37 | } 38 | } 39 | class Max extends Base{ 40 | public function __construct(){ 41 | parent::__construct(); 42 | } 43 | // arr 要调整的数组资源 44 | // from 根节点的偏移量 45 | // to 子树的偏移量 46 | // fn 函数 47 | public static function adjust( &$arr, $from, $to, $fn ){ 48 | //$tempValue = $fn( $arr[ $from ] ); 49 | $rootValue = $arr[ $from ]; 50 | // parent 是根节点,parent * 2是左子树, ( parent * 2 ) + 1是右子树 51 | // 对于此处来说,收到的只有父节点而已,根据父节点来判断子节点而并不是直接将子节点当参数传输进来 52 | //echo 'from : '.$from.PHP_EOL; 53 | for( $parent = $from; $parent * 2 <= $to; $parent = $child ){ 54 | $child = $parent * 2; 55 | if( $child < $to && $arr[ $child ] < $arr[ $child + 1 ] ){ 56 | $child++; 57 | } 58 | //echo $parent.' : '.$child.PHP_EOL.PHP_EOL; 59 | // 如果根节点比子节点小,那么交换二者数据 60 | if( $rootValue < $arr[ $child ] ){ 61 | $temp = $arr[ $parent ]; 62 | $arr[ $parent ] = $arr[ $child ]; 63 | $arr[ $child ] = $temp; 64 | } else { 65 | //echo "避免重复比较".PHP_EOL; 66 | break; 67 | } 68 | } 69 | } 70 | public static function array2max( array $arr ){ 71 | $max = new Max(); 72 | $max->size = count( $arr ); 73 | array_unshift( $arr, null ); 74 | //print_r( $arr );exit; 75 | // 开始循环数组 76 | for( $i = $max->size; $i > 0; $i-- ){ 77 | // 这个循环就是用来 确定每个节点以及其父节点的 78 | $parentIndex = floor( $i / 2 ); 79 | if( $parentIndex > 0 ){ 80 | //echo $parentIndex.PHP_EOL; 81 | self::adjust( $arr, $parentIndex, $max->size, $max->fn ); 82 | } 83 | } 84 | $max->element = $arr; 85 | //print_r( $max->element ); 86 | return $max; 87 | } 88 | public function delete(){ 89 | $data = $this->element[ 1 ]; 90 | $this->element[ 1 ] = $this->element[ $this->size ]; 91 | self::adjust( $this->element, 1, $this->size-1, $this->fn ); 92 | unset( $this->element[ $this->size ] ); 93 | $this->size--; 94 | return $data; 95 | } 96 | public function insert( $data ){ 97 | $this->size++; 98 | $this->element[ $this->size ] = $data; 99 | for( $i = $this->size; $i > 0; $i-- ){ 100 | $parentIndex = floor( $i / 2 ); 101 | if( $parentIndex > 0 ){ 102 | self::adjust( $this->element, $parentIndex, $this->size, $this->fn ); 103 | } 104 | } 105 | } 106 | } 107 | $max = Max::array2max( array( 50, 10, 90, 30, 70, 40, 80, 60, 20 ) ); 108 | print_r( $max->element ); 109 | $max->insert( 5 ); 110 | print_r( $max->element ); 111 | $max->delete(); 112 | print_r( $max->element ); 113 | ``` 114 | 115 | 实际上,有为青年可能已经研究到了,php的SPL中已经内置了Heap这种数据结构,我就贴个链接,你们感受一下:[点击这里](http://php.net/manual/en/spl.datastructures.php "点击这里") 116 | -------------------------------------------------------------------------------- /13.基础查找之二分查找.md: -------------------------------------------------------------------------------- 1 | 其实,有为青年在参与陌生商品价格竞猜节目的时候,是有技巧的。一贯场景可能都是这样的: 2 | 3 | - 拿着话筒的主持人 4 | 5 | ![](http://static.ti-node.com/6415022047126093825) 6 | 7 | - 高端的不锈钢不粘锅 8 | 9 | ![](http://static.ti-node.com/6415022327754391552) 10 | 11 | - 有为青年 12 | 13 | ![](http://static.ti-node.com/6415022494087905281) 14 | 15 | 一般情况下,游戏规则是有为青年在规定时间内猜测这个商品的价格,最后猜到的价格离这个商品的真实价格越近越好,越近奖励越丰厚,最后大奖就是把这个铁锅抱回家。 16 | 17 | 所以,在经过女主持人一顿操作后,竞猜开始: 18 | 有为青年:“500!” 19 | 女主持人:“高了” 20 | 有为青年:“250!” 21 | 女主持人:“高了” 22 | 有为青年:“125!” 23 | 女主持人:“低了” 24 | 有为青年:“ ( 125 + 250 ) / 2!” 25 | 女主持人:“高了!” 26 | 有为青年:“ ( 125 + ( 125 + 250 ) / 2 ) / 2!” 27 | 女主持人:“中了!” 28 | 29 | 作为普通青年,我们学习一下有为青年这种风骚的操作手法:也就是比较有名的二分查找。当然了,作为二分查找,是有一个前提的,那就是要查找的数据列表本身就是有序才行,不然,这个东西并没有什么卵用。 30 | 31 | 道理是比较简单浅显易懂粗俗不堪,下面,我们通过手敲代码来感受一下: 32 | 33 | ```php 34 | $arr[ $mid_index ] ){ 49 | // 将起始位置偏移量更改为 中间位置+1 50 | $begin_index = $mid_index + 1; 51 | } 52 | // 如果要找的值 比 中间位置的数值小 53 | else if( $data < $arr[ $mid_index ] ){ 54 | // 将末尾位置偏移量更改为 中间位置-1 55 | $end_index = $mid_index - 1; 56 | } 57 | // 如果等于了,也就是找到了 58 | else if( $data == $arr[ $mid_index ] ){ 59 | return $mid_index; 60 | } 61 | } 62 | } 63 | echo find( $arr, 6 ); 64 | echo PHP_EOL; 65 | echo find( $arr, 5 ); 66 | echo PHP_EOL; 67 | ``` 68 | 69 | 上述代码,值得注意的有两处: 70 | - 11行,while循环的终止条件,这里务必注意,一定是有等号的 71 | - 13行,中间位置的取值。可能会有同学觉着这里会不会漏过一些数据,实际上不会的,因为11行的约束。13行这里你甚至改成ceil或者floor都没有问题的 72 | 73 | 为什么突然这个时候冒出来一个二分查找呢?如果你仔细想想地话,上述查找过程就已经勾画出了一个二叉树,所以说,二分查找的时间复杂度是(logN)。因为二叉树我们前面好歹已经多多少少接触过一些了,所以引入二分查找就可以作为补充弥补一下了。 74 | -------------------------------------------------------------------------------- /14.排序篇之快速排序之入门篇.md: -------------------------------------------------------------------------------- 1 | 没吃过猪肉,总得见过猪跑。快速排序之所以这么被众所周知,除了性能略屌之外,还因为面试必备所以为大家所知。 2 | 3 | ![](http://static.ti-node.com/6415151902215897088) 4 | 5 | 标题叫做入门篇,因为快排其实内含的原理和思想很深邃也很多,分治和递归都是有所体现的。快排的主题中心思想就是首先选中一个基准数(不作死的话,我建议你选中间那个),然后将剩余的挨个和基准数比大小,凡是比基准数大的放到一侧,凡是比基准数小的放到另一侧,然后再对这两侧的数据集重复上面的操作。一波儿操作猛如虎后,数据就能被按照一定顺序排好了。 6 | 7 | 所以,一般说来,可以尝试通过递归来解决一下核心问题,你们感受一下: 8 | 9 | ```php 10 | > 的含义 18 | $mid_index = $length >> 1; 19 | $left = []; 20 | $right = []; 21 | for( $i = 0; $i < $length; $i++ ){ 22 | if( $i == $mid_index ){ 23 | continue; 24 | } 25 | // 如果比基准数大 26 | if( $arr[ $i ] > $arr[ $mid_index ] ){ 27 | $right[] = $arr[ $i ]; 28 | } 29 | // 如果比基准数小 30 | else if( $arr[ $i ] < $arr[ $mid_index ] ){ 31 | $left[] = $arr[ $i ]; 32 | } 33 | } 34 | return array_merge( $left, array( $arr[ $mid_index ] ), $right ); 35 | } 36 | print_r( quick( $arr ) ); 37 | ``` 38 | 39 | 运行一下,你们看到结果如下: 40 | 41 | ![](http://static.ti-node.com/6415155388433301505) 42 | 43 | 嗯,是的,上述代码有问题,我故意这么干的。实际上,有为青年大概能看出来这是将数组以234为基准数进行了一轮排列,所以比234小的都已经到前排变成了left数组,比234大的都到后排变成了right数组。但是,left和right数组本身却依旧是乱序的,那么下一步要做的就是对left和right也进行相同的操作即可! 44 | 45 | 所以,修改上述代码: 46 | 47 | ```php 48 | > 的含义 56 | $mid_index = $length >> 1; 57 | $left = []; 58 | $right = []; 59 | for( $i = 0; $i < $length; $i++ ){ 60 | if( $i == $mid_index ){ 61 | continue; 62 | } 63 | // 如果比基准数大 64 | if( $arr[ $i ] > $arr[ $mid_index ] ){ 65 | $right[] = $arr[ $i ]; 66 | } 67 | // 如果比基准数小 68 | else if( $arr[ $i ] < $arr[ $mid_index ] ){ 69 | $left[] = $arr[ $i ]; 70 | } 71 | } 72 | return array_merge( quick( $left ), array( $arr[ $mid_index ] ), quick( $right ) ); 73 | } 74 | print_r( quick( $arr ) ); 75 | ``` 76 | 77 | 其中,上述代码的第72行,请仔细品悟。比如我问个问题,是quick( $left )先执行?还是quick( $right )先执行?又或者,他们是一边递归一边merge还是等所有排完了再最后merge? 78 | -------------------------------------------------------------------------------- /15.基础查找之索引入门.md: -------------------------------------------------------------------------------- 1 | 前面铺垫了这么久,终于熬到这一章节了!学了那么多基础数据结构和简单算法,为的是什么?! 2 | 3 | 来,大声告诉我,为的是什么! 4 | 5 | ![](http://static.ti-node.com/6415366782038573057) 6 | 7 | 当然能!!!比如下面这位,就娶到了一位美丽善良的亚裔,可谓成功典范: 8 | 9 | ![](http://static.ti-node.com/6415367274575691776) 10 | 11 | 看到这个,泥,还在犹豫什么? 12 | 13 | 其实说到底,搞这么多枯燥的玩意为的就是用,我的精神领袖王守仁曾经说过“知行合一”,我的精神导师毛泽东也说过“实践是检验真理的唯一标准”。 14 | 15 | 作为和我一样的蠢货青年,在面试的时候常被人问到:“mysql有几种存储引擎?”、“索引有几种类型?”,如果是这两个问题,那就还好,路上已经背诵的差不多了,可以操作一波儿应付一下,结果,人家又问:“索引优化有什么经验吗?”,使劲想回忆,结结巴巴背诵了几点网上抄来抄去的那几条金科玉律,本以为可以蒙混过关了,结果人家放大招了:“尝试通过原理解释一下为什么这样优化索引” 16 | 17 | ![](http://static.ti-node.com/6345443000872599553)![](http://static.ti-node.com/6345443000872599553)![](http://static.ti-node.com/6345443000872599553)![](http://static.ti-node.com/6345443000872599553)![](http://static.ti-node.com/6345443000872599553)![](http://static.ti-node.com/6345443000872599553) 18 | 19 | 今天,我们尝试自己实现最简单粗暴入门的索引,从而循序渐进地继续深入探讨。首先,我们做一个简单的数据库系统,功能就是记录姓名、年龄和性别,我们将数据以json格式存储到一个文本文件中,大概类似于下面这样: 20 | 21 | [{"name":"user9351","age":45,"gender":2},{"name":"user1768","age":71,"gender":2}] 22 | 23 | 我们将100000个人存入到这个文本文件中,如何制造这个json数据我就不讲了,你们自己操作。下面问题来了,就是我要从中找到某个用户,比如user5555(当然了,你得保证其中一定有这个用户),那么我们的做法大概如下: 24 | 25 | ```php 26 | 'user'.$user_suf, 'age' => mt_rand( 1, 100 ), 'gender' => mt_rand( 1, 2 ), ]; 40 | } 41 | $user = json_encode( $user ); 42 | file_put_contents( './user.json', $user ); 43 | exit; 44 | */ 45 | 46 | $begin_time = microtime( true ); 47 | // 读取数据 48 | $user = file_get_contents( './user.json' ); 49 | $user = json_decode( $user, true ); 50 | // 假如很短时间内500个人并发查询 51 | // 查找同一个用户名不会有缓存,所以不用考虑缓存影响速度的问题 52 | for( $i = 1; $i <= 500; $i++ ){ 53 | foreach( $user as $user_item ){ 54 | if( 'user1692145' == $user_item['name'] ){ 55 | //echo '找到了'.PHP_EOL; 56 | } 57 | } 58 | } 59 | $end_time = microtime( true ); 60 | echo '耗费时间'.( $end_time - $begin_time ).PHP_EOL; 61 | ``` 62 | 63 | 保存后运行一把,我这里机器显示如下: 64 | 65 | ![](http://static.ti-node.com/6415391831957176321) 66 | 67 | 短时间内500个查找用5.6秒钟多,要是流量再大点儿,而且用户找的数据分布都偏后半部分,这个成绩一定会更差。所以,作为有为青年就会考虑使用一种如何加速查找。观察了一下name字段的数据特点,那就是前四个字母是固定的,后缀数字是随机且唯一的,于是打算将后缀数字和用户在数组中的位置偏移量做个对应关系,大概如下所示: 68 | 69 | user数字后缀 =》 user信息在数组中的index偏移量 70 | 71 | 然后,将改数组以user数字后缀为关键列从小到大排列一下,保存起来。以后当有人查找user1312312的时候,就会通过二分查找直接从“user数字后缀 =》 user信息在数组中的index偏移量”映射表中去查找1312312,从而我们可以获取到user1312312在user数组中的偏移量,有了偏移量我们就能以O(1)时间复杂度获取到想要的用户信息了! 72 | 73 | 其实,这就是一种简单的索引技术了!只不过,此处索引的维护建立我们依靠了php的数组数据结构,有投机取巧之嫌,但道理是这样的摆在这里了,重在理解吧。有能力的同学,可以通过C语言等来自己实现索引会更好一些。 74 | 75 | 心动,不如行动! 76 | 77 | ```php 78 | 'user'.$user_suf, 'age' => mt_rand( 1, 100 ), 'gender' => mt_rand( 1, 2 ), ]; 95 | $uid_index_arr[ $user_suf ] = $i; 96 | 97 | } 98 | $user = json_encode( $user ); 99 | file_put_contents( './user.json', $user ); 100 | file_put_contents( './user.index.json', json_encode( $uid_index_arr ) ); 101 | exit; 102 | */ 103 | 104 | $begin_time = microtime( true ); 105 | // 读取数据 106 | $user = file_get_contents( './user.json' ); 107 | $user = json_decode( $user, true ); 108 | // 读取索引 109 | $index = file_get_contents( './user.index.json' ); 110 | $index = json_decode( $index, true ); 111 | // 假如很短时间内500个人并发查询 112 | // 查找同一个用户名不会有缓存,所以不用考虑缓存影响速度的问题 113 | for( $i = 1; $i <= 500; $i++ ){ 114 | $suf = substr( 'user1948829', 4 ); 115 | // 在索引文件中查找对应关系 116 | $_index = $index[ $suf ]; 117 | $_user = $user[ $_index ]; 118 | echo $_user['age'].PHP_EOL; 119 | } 120 | $end_time = microtime( true ); 121 | echo '耗费时间'.( $end_time - $begin_time ).PHP_EOL; 122 | ``` 123 | 124 | 代码保存后执行,我们看到如下: 125 | 126 | ![](http://static.ti-node.com/6415406210090008576) 127 | 128 | 进步神速! 129 | 130 | 但是,代价也是有的,首先就是我们还需要额外浪费磁盘空间去维护一个索引文件,其次是如果有新的数据加入或者删除,这个索引文件就必须要重新更新一下。只不过相对于速度的提升程度,这点儿额外的代价都不算什么了,利益远远大于损失。 131 | 132 | 在结束前,放点儿需要背诵或者眼熟的内容:一般说来,索引根据类型可以分成线性索引、树型索引、多级索引。我们今天案例中简单这个算是线性索引。线性索引有个巨大的缺点,就是有多少条数据就需要对应多少条索引记录。而树型索引就比较屌了,我们常说的mysql数据库中的一些索引,本质上就是树型索引。 133 | -------------------------------------------------------------------------------- /2.排序篇之选择排序.md: -------------------------------------------------------------------------------- 1 | 你王老师始终是你王老师,你自以为期中考试你的答辩题目弄成《论冒泡排序在社会主义建设中的贡献与作用》就可以拿到A+级了,然并卵,王老师还是仅仅给了你一个C及格级。 2 | 3 | 毕竟,王老师那是见得多了,区区冒泡就敢来装高端搞大新闻?回家再修炼两年吧。 4 | 5 | ![](http://static.ti-node.com/6401402899972227073) 6 | 7 | 选择排序相对来说是一种思路比较简单粗暴明了清晰的排序算法,主题思路就是:从一坨数字中找出最小的放到第一位,然后从剩余数字中再找最小的放到第二位,依次继续,知道排序完毕。 8 | 9 | 这不是特么废话么,哪个排序算法不是这么干的? 10 | - 首先,我们定义一个基准数A,并假装TA暂时就是最小的那个(一般情况自己不作死的话,就选第一个),定义一个变量min_index并将A的数组索引位置赋值给min_index 11 | - 然后,我们将min_index索引位置上的数字(第一轮其实就是A)依次(开始内循环)和剩余其他所有数字比较大小,如果基准数比某数字大了,那么(注意了⚠️⚠️⚠️)我们将某数字所在的数组索引位置赋值给min_index 12 | - 然后,现在最小值俨然已经发生了变化,然后循环继续重复前两个步骤一直到本轮终止结束 13 | 14 | 15 | 不管你能不能看得懂我画的啥意思,反正我强烈你一定要画一画,纸笔演练: 16 | ![](http://static.ti-node.com/6401433562721026048) 17 | 18 | 19 | ```php 20 | $arr[ $inner ] ){ 31 | $min_index = $inner; 32 | } 33 | } 34 | // 等所有内循环跑完毕后,判断最小数字的索引位置和 outer 是否相同,如果相同,说明outer位置上数就是最小的 35 | // 如果不一样,那么,min_index和outer两个位置的数字 36 | if( $min_index != $outer ){ 37 | $temp = $arr[ $outer ]; 38 | $arr[ $outer ] = $arr[ $min_index ]; 39 | $arr[ $min_index ] = $temp; 40 | } 41 | } 42 | print_r( $arr ); 43 | ``` 44 | 45 | 那么,话说回来,这个排序算法为啥就比冒泡排序屌呢?***这里先卖个关子,最后总结的时候出个汇总***。现在就临时用下面的代码做个简单的测试吧: 46 | ```php 47 | $outer; $inner-- ){ 54 | if( $arr[ $inner ] < $arr[ $inner - 1 ] ){ 55 | $temp = $arr[ $inner ]; 56 | $arr[ $inner ] = $arr[ $inner - 1 ]; 57 | $arr[ $inner - 1 ] = $temp; 58 | $flag = true; 59 | } 60 | } 61 | } 62 | return $arr; 63 | } 64 | function select( $arr ){ 65 | $length = count( $arr ); 66 | for( $outer = 0; $outer < $length - 1; $outer++ ){ 67 | $min_index = $outer; 68 | for( $inner = $outer + 1; $inner < $length; $inner++ ){ 69 | if( $arr[ $min_index ] > $arr[ $inner ] ){ 70 | $min_index = $inner; 71 | } 72 | } 73 | if( $min_index != $outer ){ 74 | $temp = $arr[ $outer ]; 75 | $arr[ $outer ] = $arr[ $min_index ]; 76 | $arr[ $min_index ] = $temp; 77 | } 78 | } 79 | return $arr; 80 | } 81 | $length = 200; 82 | for( $i = 0; $i < $length; $i++ ){ 83 | $arr[] = mt_rand( 1,1000); 84 | } 85 | // 冒泡算法 86 | $begin = microtime( true ); 87 | for( $i = 0; $i < $length; $i++ ){ 88 | bubble( $arr ); 89 | } 90 | $end = microtime( true ); 91 | echo PHP_EOL.PHP_EOL.PHP_EOL."冒泡算法耗费时间:".( $end - $begin ).PHP_EOL; 92 | // 选择算法 93 | $begin = microtime( true ); 94 | for( $i = 0; $i < $length; $i++ ){ 95 | select( $arr ); 96 | } 97 | $end = microtime( true ); 98 | echo "选择算法耗费时间:".( $end - $begin ).PHP_EOL.PHP_EOL.PHP_EOL; 99 | ``` 100 | 运行结果如下图所示: 101 | 102 | ![](http://static.ti-node.com/6401429588672512001) 103 | 104 | 200条数据,就能差出一半时间来,吓人不?王老师终究是你王老师,嫩还是嫩了点儿的。 105 | 106 | ------ 107 | -------------------------------------------------------------------------------- /3.排序篇之直接插入排序.md: -------------------------------------------------------------------------------- 1 | ##### “很多年以后,奥雷连诺上校站在行刑队面前,准会想起父亲带他去参观冰块的那个遥远的下午。” ------ 节选自西亚·马尔克斯《百年孤独》 2 | 3 | 当一个泥腿子出身的码了三年的php的phper头一次来到BAT面试的时候,似乎听到看到人家都在用O(n)、O(n^2)抑或AVL、B+Tree、Huffuman之类的词语彼此交流。我能想象出来,在面对面试官的时候,瑟瑟发抖的你被对方的知识体系所笼罩,对方在向你展示冰块,而你,也真是第一次见到冰块。 4 | 5 | ![](http://static.ti-node.com/6401436212493549568) 6 | 7 | 不过,话说回来,咱老李也不是个孬种,就算咱老李是编筐专业毕业的,到头来不照样带兵干仗? 8 | 9 | ![](http://static.ti-node.com/6401437735126564864) 10 | 11 | 话再说回来,即便是科班的,也有暂七师不是? 12 | 13 | ![](http://static.ti-node.com/6401438443213160448) 14 | 15 | 不过,涨涨见识总归还是不错的。一个冒泡排序和一个选择排序,能够初步给你揭开算法的奥妙和重要。王老师是把你带进门了,至于后面怎么着,全看你自己的造化了。 16 | 17 | 对于直接插入排序,实际上我一直怀疑就是一个程序员在打扑克的时候发明的,因为这整个逻辑过程类似于抽牌。你跟丁伟、老赵玩斗地主,你抽到牌有♥️4、♠️9、♣️6、♣️10、♦️1,作为处女座的你,看到这样一副凌乱的牌自然心里是极不舒服的,所以你决定操作操作一波儿规整一下牌,你看了看9,比4小,不用管了,看到了6,放到4和9中间,看到10,不用动,最后拿1,直接放到了4前面,于是牌变成了♦️1、♥️4、♣️6、♠️9、♣️10,心里终于舒服了。 18 | 19 | 抽象思维归抽象思维,每次运用到实践上,总是写不出来,发愁。所以为了加深理解,还是老套路,乖乖地纸笔演练吧: 20 | ![](http://static.ti-node.com/6401467285353005057) 21 | 22 | ```php 23 | = 0 && $arr[ $inner ] > $temp; $inner-- ){ 30 | $arr[ $inner + 1 ] = $arr[ $inner ]; 31 | } 32 | $arr[ $inner + 1 ] = $temp; 33 | } 34 | } 35 | print_r( $arr ); 36 | ``` 37 | 运行结果如下图所示: 38 | 39 | ![](http://static.ti-node.com/6401467678069882881) 40 | 41 | 最后,咱旅长心疼咱老李,送了咱一张直接插入排序的动态图,你们感受一下: 42 | 43 | ![](http://static.ti-node.com/6401468341294202881) 44 | -------------------------------------------------------------------------------- /4.数据结构之线性表(顺序存储表).md: -------------------------------------------------------------------------------- 1 | 为啥突然不写排序了,不是听说还有好几种别的排序办法吗?主要是剩下几种算法是和一些数据结构挂钩的,不明白这些数据结构是很难搞明白这些排序算法的。 2 | 3 | 第一篇线性表是一个比较简单的概念,大概意思就是“ 零个或多个数据元素的有限序列 ”,值得注意的是这些数据是有先后顺序的,并不是一坨乱而无序的数字。 4 | 5 | 举个简单的例子吧,准备开始练球了,达叔让大师兄、二师兄、三师兄、五师弟、六师弟拍成一队,然后大师兄发球给二师兄,二师兄转球给三师兄,三师兄转球给你,你转球给五师弟,五师弟转球给六师弟,到了六师弟这里就算一轮练习完成,我们说这种前后有顺序的队列可以称作线性表,在这个队伍中,如果球到从大师兄脚下到五师弟这里,就必须严格按照大师兄 -> 二师兄 -> 三师兄 -> 我 -> 五师弟这样的顺序来传球,不能出现大师兄直接传球给五师弟等现象。 6 | 7 | ![](http://static.ti-node.com/6401618099149209600) 8 | 9 | 在练习了一段时间后,达叔让大家到球场上和著名业余球队踢了以切磋为主的“ 友谊第一,比赛第二 ”的球赛,这个球赛中,少林队传球就不能讲究顺序了,而是任意一个球员都可以向其他任意队友传球。这种情况下,就不能满足线性表的一些定义和概念了。 10 | 11 | ![](http://static.ti-node.com/6401621180733718528) 12 | 13 | 线性表有两种存储方式: 14 | - 顺序存储方式,顺序存储的方式就是在内存中开辟一块儿地址连续的存储空间用来容纳数据。举个例子,就像观众在排队买 少林队 VS 豆腐金刚队 的门票,由于赛况激烈所以采取抢购策略,来了十个抢票的人,这个时候保安就得腾出一块儿能容纳下十个人地儿来。如果突然一个美女要插队买票,这事儿就比较麻烦了。既然是插队,也就说肯定不是在队尾了,假如美女靠美色插队到了第五个人前面,那么后面的人就要统统往后退一个位置了,一下要六个人往后后退一步。 15 | - 链式存储方式,对于上述顺序存储的问题,该如何解决呢?还是少林队 VS 金刚豆腐队的事儿,官方这次学聪明了,这次官方让大家通过网络预约号码,号码上同时还有下一个人的人名。这下好了,门口的空间可以好好利用了,大家可以随意占了没有必要一定要排队。观众强雄在买完票后,他会喊下一个观众酱爆,至于酱爆在什么地方就不一定了,他不一定非要站在强雄后面。这种方式的好处就是,每个人都可以占据在自己想在的地方,门口的空间利用率会更好更高。这个时候,如果再有美女来插队,那也变得简单的很了。比如她插队到酱爆前面,就只需要把强雄票上的下一个人的人名换成美女,然后美女票上下一个人的人名换成酱爆就可以了。 16 | 17 | ![](http://static.ti-node.com/6401653572303323137) 18 | 19 | 现在我们先抽象一下顺序存储线性表的几个重要操作: 20 | - 添加元素 21 | - 删除元素 22 | - 查看元素数量 23 | - 获取某个位置上的数据 24 | - 根据数据获取数据位置 25 | - 更改某个位置上的数据 26 | - 清空线性表 27 | 28 | 其实,在php中,数组就可以用来当作顺序存储表,大家想想是不是?然而,作为一个有尺度有态度的php文化传播圣地,不弄点儿高端的实在是对不起自己的。来吧,动手吧。 29 | ```php 30 | get_length(); 40 | if( $index < 0 || $index > $length ){ 41 | return false; 42 | } 43 | // 将数字统统往后移动一个位置 44 | for( $i = $length; $i > $index; $i-- ){ 45 | $this->item[ $i ] = $this->item[ $i - 1 ]; 46 | } 47 | $this->item[ $index ] = $item; 48 | return true; 49 | } 50 | /* 51 | * @desc : 删除某个位置的元素 52 | * @param : index 53 | */ 54 | public function delete_item( $index ){ 55 | $length = $this->get_length(); 56 | if( $index < 0 || $index > $length - 1 ){ 57 | return false; 58 | } 59 | $value = $this->item[ $index ]; 60 | // 将数字统统往前移动一个位置 61 | for( $i = $index; $i < $length - 1; $i++ ){ 62 | $this->item[ $i ] = $this->item[ $i + 1 ]; 63 | } 64 | unset( $this->item[ $length - 1 ] ); 65 | return $value; 66 | } 67 | /* 68 | * @desc : 获取长度 69 | * @param : index 70 | */ 71 | public function get_length(){ 72 | return count( $this->item ); 73 | } 74 | /* 75 | * @desc : 根据位置获取元素 76 | * @param : index 77 | */ 78 | public function get_item_by_index( $index ){ 79 | $length = $this->get_length(); 80 | if( $index < 0 || $index >= $length ){ 81 | return null; 82 | } 83 | return $this->item[ $index ]; 84 | } 85 | /* 86 | * @desc : 根据元素获取位置 87 | * @param : item 88 | */ 89 | public function get_index_by_item( $item ){ 90 | // 这个没什么好办法,只能是通过遍历所有数据来做了 91 | $length = $this->get_length(); 92 | $index = null; 93 | for( $i = 0; $i < $length; $i++ ){ 94 | if( $item == $this->item[ $i ] ){ 95 | $index = $i; 96 | } 97 | } 98 | return $index; 99 | } 100 | /* 101 | * @desc : 更新某个位置上的元素 102 | * @param : index 103 | * @param : item 104 | */ 105 | public function update_item_by_index( $index, $item ){ 106 | $length = $this->get_length; 107 | if( $index < 0 || $index >= $length ){ 108 | return false; 109 | } 110 | $this->item[ $index ] = $item; 111 | } 112 | /* 113 | * @desc : 获取线性表中所有元素 114 | */ 115 | public function get_all_item(){ 116 | // 你以为要用print_r或者var_dump,然而,为了装逼,并不能,我们要遍历,遍历,遍历! 117 | $length = $this->get_length(); 118 | for( $i = 0; $i < $length; $i++ ){ 119 | echo $this->item[ $i ].' '; 120 | } 121 | echo PHP_EOL; 122 | } 123 | /* 124 | * @desc : 清空一个线性表 125 | */ 126 | public function truncate(){ 127 | $this->item = null; 128 | } 129 | } 130 | 131 | $line = new Linear(); 132 | // 连续添加5个元素 133 | $line->add_item( 0, 12 ); 134 | $line->add_item( 1, 2 ); 135 | $line->add_item( 2, 5 ); 136 | $line->add_item( 3, 6 ); 137 | $line->add_item( 4, 1 ); 138 | // 获取所有元素 139 | $line->get_all_item(); 140 | // 向位置2上插入一个新的元素8 141 | $line->add_item( 2, 8 ); 142 | // 获取所有元素 143 | $line->get_all_item(); 144 | // 获取3位置上的元素 145 | echo $line->get_item_by_index( 3 ).PHP_EOL; 146 | // 获取元素5的位置 147 | echo $line->get_index_by_item( 5 ).PHP_EOL; 148 | // 删除位置3上的元素 149 | $line->delete_item( 3 ); 150 | // 获取所有元素 151 | $line->get_all_item(); 152 | // 获取表的长度 153 | echo '表的长度:'.$line->get_length().PHP_EOL; 154 | ``` 155 | 156 | 懒的人分为好几种: 157 | - 看了看文章,记住了些许理论原理,觉得了然于胸的 158 | - 看了看文章直接复制粘贴代码运行了一下的 159 | - 看了看文章,自己用php array函数走了一遍的 160 | - 看了看文章,自己对着上述代码敲了一遍的 161 | - 看了看文章,理解了原理,看了一眼代码,自己憋代码,实在憋不出的再回来看代码 162 | 163 | ### 哈哈哈哈哈哈哈,实际上我的代码有好几处错的,懒的人早晚栽坑 164 | 165 | ------ 166 | -------------------------------------------------------------------------------- /5.数据结构之线性表(链式存储表).md: -------------------------------------------------------------------------------- 1 | 下午抽空去面了一家做区块链的公司,算是我朋友的一个公司吧,只不过他是做前端的,流程还是要走走。看起来公司正在极速扩张中,本来我也准备好要手撕TCP协议、基础数据结构算法之类的,但是面试的小孩子反而自己紧张地说话都发抖,一时间让我觉得特别尴尬,一定程度上我还得反过来安慰他。 2 | 3 | 比较累,所以单链表可能会稍微水些许,不过主要内容不会拉下,见谅! 4 | 5 | 好了,回到正路来,就是采用了链式存储的顺序表(又叫单链表),单链表的组成元素我们可以称之为结点。从逻辑上看,单链表中的结点是一个顺序的数字队伍。从物理存储角度看,这些结点则是分布在连续或者不连续的内存区域中,彼此之间依靠指针来连接。 6 | 7 | 上一章节中说过了,少林队 VS 金刚豆腐队的门票采用了网络预约电子发售的方式进行的,这种电子票上需要记录两种信息,一个是观看者本身的个人资料信息,同时还有下一个买到票的人的名字。比如强雄的票上除了有强雄自己的个人数据外,还有下一个买到票的人的名字:酱爆。 8 | 9 | 票就相当于是结点,一个结点往往由两部分组成,一部分是数据本身,另一部分就是指向下一个结点的指针。如果A结点往后没有结点了怎么办?不怎么办,把A结点的指向下一个结点的指针赋值为null即可。链表有一个非常重要的概念叫做头指针,一个链表必须拥有一个头指针!为什么呢?因为单链表就是靠头指针来确定的,你可以粗暴地认为没有头指针,你压根就不知道单链表在哪儿。除此之外,还有一个听起来比较朦胧的概念,叫做头结点。头结点并不是第一个结点的意思,这个东西是插在第一个结点和头指针之间的一个结点。头指针指向的是头结点,而头结点的指针区域则指向第一个结点,而头结点到数据区域中则可以存储一些比较自由的数据了,说直白点儿就是你爱存啥就存啥,没人管你。 10 | 11 | 单链存储表比顺序存储表的好处上文已经指出过了,下面定义几个单链表的几个关键操作: 12 | - 获取单链表长度 13 | - 从单链表中删除某个结点 14 | - 向单链表中添加一个结点 15 | - 清空整个单链表 16 | - 获取某个位置上的结点 17 | 18 | 在php代码演示中,我们使用两个class来完成单链表的一些操作。第一个是Node类,这个类就是结点的数据结构抽象。第二个就是Linked_sq类,这个类就是单链表以及单链表的操作。 19 | ```php 20 | data = $data; 27 | } 28 | } 29 | // 单链表的存储 30 | class Linked_sq{ 31 | private $head = null; 32 | public function __construct(){ 33 | // 添加一个头结点 34 | $this->head = new Node(); 35 | } 36 | /* 37 | * @desc : 获取单链表长度 38 | */ 39 | public function get_length(){ 40 | // 注意 头结点 不算在单链表的长度内 41 | $length = 0; 42 | $head = $this->head; 43 | while( $head->next ){ 44 | $head = $head->next; 45 | $length++; 46 | } 47 | return $length; 48 | } 49 | /* 50 | * @desc : 添加一个结点 51 | * @param : index 52 | * @param : item 53 | */ 54 | public function add_item( $index, $item ){ 55 | $length = $this->get_length(); 56 | if( $index < 0 || $index > $length ){ 57 | return false; 58 | } 59 | // 获取头结点 60 | $head = $this->head; 61 | for( $i = 0; $i < $index; $i++){ 62 | $head = $head->next; 63 | } 64 | // 初始化一个新结点 65 | $node = new Node( $item ); 66 | $node->next = $head->next; 67 | $head->next = $node; 68 | return true; 69 | } 70 | /* 71 | * @desc : 删除一个结点 72 | * @param : index 73 | */ 74 | public function delete_item( $index ){ 75 | $length = $this->get_length(); 76 | if( $index < 0 || $index > $length ){ 77 | return null; 78 | } 79 | $head = $this->head; 80 | // 这里循环的终止条件一定要想明白了,我要删除index位置上的结点,从0开始寻找一直到index这个结点,但是 81 | // 当循环终止的时候,被选中的结点是 index - 1 位置上的结点 82 | for( $i = 0; $i < $index; $i++ ){ 83 | $head = $head->next; 84 | } 85 | // 这个才是要被删除的结点 86 | $delete_node = $head->next; 87 | $value = $delete_node->data; 88 | // 修改指针指向 89 | $head->next = $head->next->next; 90 | // 删除结点 91 | unset( $delete_node ); 92 | return $value; 93 | } 94 | /* 95 | * @desc : 查找某个位置上的数值 96 | * @param : index 97 | */ 98 | public function get_item_by_index( $index ){ 99 | $length = $this->get_length(); 100 | if( $index < 0 || $index > $length ){ 101 | return null; 102 | } 103 | $head = $this->head; 104 | // 注意这里终止条件,这里获取到的是 index-1 位置上的结点 105 | for( $i = 0; $i < $index; $i++ ){ 106 | $head = $head->next; 107 | } 108 | return $head->next->data; 109 | } 110 | /* 111 | * @desc : 清空整个单链表 112 | */ 113 | public function truncate(){ 114 | // 第一个结点,就是头结点 115 | $head = $this->head; 116 | while( $head->next ){ 117 | $temp = $head->next; 118 | unset( $head->next ); 119 | $head = $temp; 120 | } 121 | return true; 122 | } 123 | 124 | } 125 | 126 | $link = new Linked_sq(); 127 | echo '单链表初始长度为:'.$link->get_length().PHP_EOL; 128 | // 添加一个结点 129 | $link->add_item( 0, 9 ); 130 | echo '单链表长度为:'.$link->get_length().PHP_EOL; 131 | $link->add_item( 1, 2 ); 132 | $link->add_item( 2, 12 ); 133 | $link->add_item( 3, 5 ); 134 | echo '单链表长度为:'.$link->get_length().PHP_EOL; 135 | // 获取第2个位置上的数字 136 | echo "第2个位置上的数字:".$link->get_item_by_index( 2 ).PHP_EOL; 137 | // 删除第1个位置上的数字 138 | echo "删除了".$link->delete_item( 1 ).PHP_EOL; 139 | echo '单链表长度为:'.$link->get_length().PHP_EOL; 140 | echo "第2个位置上的数字:".$link->get_item_by_index( 2 ).PHP_EOL; 141 | ``` 142 | 143 | 晚安,祝大家好梦。 144 | 145 | ---- 146 | -------------------------------------------------------------------------------- /6.数据结构之栈.md: -------------------------------------------------------------------------------- 1 | 不按照常理出牌,从理论上讲,单链表后面还有双向链表以及循环链表,但我就不按照常规来,今儿直接搞栈。 2 | 3 | 栈又叫做stack,栈也是线性表,只不过这种线性表比较特殊而已,和普通线性表唯一不一样的地方就是:如果你要删除或者添加结点,只能在线性表的尾部进行操作: 4 | - 你要添加一个新的结点,就只能往线性表最后添加,这个操作术语叫做 入栈,英文叫做push 5 | - 你要删除一个老的结点,也只能删除线性表最后一个结点,这个操作术语叫做 出栈,英文叫做pop 6 | 7 | 我知道,肯定又有人要出馊主意了,因为pop和push另他们联想到了两个php函数:array_push和array_pop。弄一个php数组,然后用这两个函数在数组最后一个元素上一顿操作猛如虎,栈不就这点儿东西么?php数组是可以用来模拟栈的,而且配合上array_push和array_pop确实可以完美实现对栈的操作。 8 | 9 | 成也php数组,败也php数组。多少人因为php array的强大,一辈子窝在了php array中。 10 | 11 | 选择了array模式的童鞋,看到这里可以出门左拐了。 12 | 13 | 困难模式,从这行开始。开局就一个线性表,能掌握多少全靠亲自码。 14 | 15 | 前面说了,栈就是线性表而已,一般说来,线性表的第一个元素可以称为栈底,线性表的最后一个元素成为栈顶。为了方便理解,我建议大家将脑海中“一”字型线性表脑补成“|”字型的,这样竖向的线性表更容易体现出“顶”和“底”的概念。 16 | 17 | 假设现在有4、5、6三个数字从头到位依次入栈,整个流程如下: 18 | 19 | ![](http://static.ti-node.com/6402002893670449153) 20 | 21 | 那么,出栈顺序整个流程就如下图: 22 | 23 | ![](http://static.ti-node.com/6402003359204638720) 24 | 25 | 代码上,相对来说就简单了,因为线性表我们前面已经写过了,我们只需要在原来的基础上添加两个方法pop和push即可(以单链表代码为基础): 26 | ```php 27 | data = $data; 34 | } 35 | } 36 | // 单链表的存储 37 | class Linked_sq{ 38 | private $head = null; 39 | public function __construct(){ 40 | // 添加一个头结点 41 | $this->head = new Node(); 42 | } 43 | /* 44 | * @desc : 获取单链表长度 45 | */ 46 | public function get_length(){ 47 | // 注意 头结点 不算在单链表的长度内 48 | $length = 0; 49 | $head = $this->head; 50 | while( $head->next ){ 51 | $head = $head->next; 52 | $length++; 53 | } 54 | return $length; 55 | } 56 | /* 57 | * @desc : 添加一个结点 58 | * @param : index 59 | * @param : item 60 | */ 61 | public function add_item( $index, $item ){ 62 | $length = $this->get_length(); 63 | if( $index < 0 || $index > $length ){ 64 | return false; 65 | } 66 | // 获取头结点 67 | $head = $this->head; 68 | for( $i = 0; $i < $index; $i++){ 69 | $head = $head->next; 70 | } 71 | // 初始化一个新结点 72 | $node = new Node( $item ); 73 | $node->next = $head->next; 74 | $head->next = $node; 75 | return true; 76 | } 77 | /* 78 | * @desc : 删除一个结点 79 | * @param : index 80 | */ 81 | public function delete_item( $index ){ 82 | $length = $this->get_length(); 83 | if( $index < 0 || $index > $length ){ 84 | return null; 85 | } 86 | $head = $this->head; 87 | // 这里循环的终止条件一定要想明白了,我要删除index位置上的结点,从0开始寻找一直到index这个结点,但是 88 | // 当循环终止的时候,被选中的结点是 index - 1 位置上的结点 89 | for( $i = 0; $i < $index; $i++ ){ 90 | $head = $head->next; 91 | } 92 | // 这个才是要被删除的结点 93 | $delete_node = $head->next; 94 | $value = $delete_node->data; 95 | // 修改指针指向 96 | $head->next = $head->next->next; 97 | // 删除结点 98 | unset( $delete_node ); 99 | return $value; 100 | } 101 | /* 102 | * @desc : 查找某个位置上的数值 103 | * @param : index 104 | */ 105 | public function get_item_by_index( $index ){ 106 | $length = $this->get_length(); 107 | if( $index < 0 || $index > $length ){ 108 | return null; 109 | } 110 | $head = $this->head; 111 | // 注意这里终止条件,这里获取到的是 index-1 位置上的结点 112 | for( $i = 0; $i < $index; $i++ ){ 113 | $head = $head->next; 114 | } 115 | return $head->next->data; 116 | } 117 | /* 118 | * @desc : 清空整个单链表 119 | */ 120 | public function truncate(){ 121 | // 第一个结点,就是头结点 122 | $head = $this->head; 123 | while( $head->next ){ 124 | $temp = $head->next; 125 | unset( $head->next ); 126 | $head = $temp; 127 | } 128 | return true; 129 | } 130 | /* 131 | * @desc : 栈的pop方法 132 | */ 133 | public function pop(){ 134 | $length = $this->get_length(); 135 | return $this->delete_item( $length - 1 ); 136 | } 137 | /* 138 | * @desc : 栈的push方法 139 | */ 140 | public function push( $item ){ 141 | $length = $this->get_length(); 142 | $this->add_item( $length, $item ); 143 | } 144 | } 145 | $link = new Linked_sq(); 146 | echo '单链表初始长度为:'.$link->get_length().PHP_EOL; 147 | // 添加一个结点 148 | $link->push( 9 ); 149 | echo '单链表长度为:'.$link->get_length().PHP_EOL; 150 | $link->push( 1 ); 151 | $link->push( 2 ); 152 | $link->push( 5 ); 153 | echo '单链表长度为:'.$link->get_length().PHP_EOL; 154 | echo '第二个位置上的:'.$link->get_item_by_index( 1 ).PHP_EOL; 155 | $link->pop(); 156 | echo '单链表长度为:'.$link->get_length().PHP_EOL; 157 | ``` 158 | 159 | 我们在单链表的代码基础上添加了pop和push两个新方法,而且这两个方法本质上也是将原有的add_item和delete_item两个方法包装了一下。 160 | 161 | ### 单纯看栈的用法大致就这点儿东西,下一篇章结合点儿栈的实际用例。 162 | 163 | --- 164 | -------------------------------------------------------------------------------- /7.数据结构之队列.md: -------------------------------------------------------------------------------- 1 | 啦啦啦,啦啦啦,这将是线性表系列文章的最后一篇了,就是队列。flag也先立了,队列也是线性表,一种有着特殊表现的线性表。 2 | 3 | 其实很多时候,现实生活中案例就是活脱脱的队列应用案例。你们去过周一到周五早上八点左右的天通苑地铁站或者周一到周五的西二旗地铁站吗?![](http://static.ti-node.com/6345443000872599553)![](http://static.ti-node.com/6345443000872599553)![](http://static.ti-node.com/6345443000872599553) 4 | 5 | 一般情况下,不出意外应该都是这样的: 6 | 7 | ![](http://static.ti-node.com/6402051506186485761) 8 | 9 | 如果你有无人机的,空中航拍一下,俯瞰效果大概可能会类似于下图: 10 | 11 | ![](http://static.ti-node.com/6402053156548640768) 12 | 13 | 如果你要添加节点,那就只能往队列的尾巴上添加新节点。如果你要删除节点,那就只能从队列的头部删除节点。这种特性,我们用先进先出(FIFO, First-In-First-Out)来形容,而上节的栈实际上是后进先出(LIFO,Last-In-First-Out)。 14 | 15 | 多说一句,为啥地铁站要这么干?实际上就是为了限流。这和大家嘴中常说的利用消息队列限流保证服务不会冲跨就是一个道理。 16 | 17 | 由于队列本质上也是线性表,所以我们依然拿单链表的代码进行修改,摇身一变变成队列: 18 | ```php 19 | data = $data; 26 | } 27 | } 28 | // 单链表的存储 29 | class Linked_sq{ 30 | private $head = null; 31 | public function __construct(){ 32 | // 添加一个头结点 33 | $this->head = new Node(); 34 | } 35 | /* 36 | * @desc : 获取单链表长度 37 | */ 38 | public function get_length(){ 39 | // 注意 头结点 不算在单链表的长度内 40 | $length = 0; 41 | $head = $this->head; 42 | while( $head->next ){ 43 | $head = $head->next; 44 | $length++; 45 | } 46 | return $length; 47 | } 48 | /* 49 | * @desc : 添加一个结点 50 | * @param : index 51 | * @param : item 52 | */ 53 | public function add_item( $index, $item ){ 54 | $length = $this->get_length(); 55 | if( $index < 0 || $index > $length ){ 56 | return false; 57 | } 58 | // 获取头结点 59 | $head = $this->head; 60 | for( $i = 0; $i < $index; $i++){ 61 | $head = $head->next; 62 | } 63 | // 初始化一个新结点 64 | $node = new Node( $item ); 65 | $node->next = $head->next; 66 | $head->next = $node; 67 | return true; 68 | } 69 | /* 70 | * @desc : 删除一个结点 71 | * @param : index 72 | */ 73 | public function delete_item( $index ){ 74 | $length = $this->get_length(); 75 | if( $index < 0 || $index > $length ){ 76 | return null; 77 | } 78 | $head = $this->head; 79 | // 这里循环的终止条件一定要想明白了,我要删除index位置上的结点,从0开始寻找一直到index这个结点,但是 80 | // 当循环终止的时候,被选中的结点是 index - 1 位置上的结点 81 | for( $i = 0; $i < $index; $i++ ){ 82 | $head = $head->next; 83 | } 84 | // 这个才是要被删除的结点 85 | $delete_node = $head->next; 86 | $value = $delete_node->data; 87 | // 修改指针指向 88 | $head->next = $head->next->next; 89 | // 删除结点 90 | unset( $delete_node ); 91 | return $value; 92 | } 93 | /* 94 | * @desc : 查找某个位置上的数值 95 | * @param : index 96 | */ 97 | public function get_item_by_index( $index ){ 98 | $length = $this->get_length(); 99 | if( $index < 0 || $index > $length ){ 100 | return null; 101 | } 102 | $head = $this->head; 103 | // 注意这里终止条件,这里获取到的是 index-1 位置上的结点 104 | for( $i = 0; $i < $index; $i++ ){ 105 | $head = $head->next; 106 | } 107 | return $head->next->data; 108 | } 109 | /* 110 | * @desc : 清空整个单链表 111 | */ 112 | public function truncate(){ 113 | // 第一个结点,就是头结点 114 | $head = $this->head; 115 | while( $head->next ){ 116 | $temp = $head->next; 117 | unset( $head->next ); 118 | $head = $temp; 119 | } 120 | return true; 121 | } 122 | /* 123 | * @desc : shift 124 | */ 125 | public function shift(){ 126 | $length = $this->get_length(); 127 | return $this->delete_item( 0 ); 128 | } 129 | /* 130 | * @desc : push 131 | */ 132 | public function push( $item ){ 133 | $length = $this->get_length(); 134 | return $this->add_item( $length, $item ); 135 | } 136 | } 137 | $link = new Linked_sq(); 138 | echo 'length : '.$link->get_length().PHP_EOL; 139 | // 添加一个结点 140 | $link->push( 9 ); 141 | echo 'length : '.$link->get_length().PHP_EOL; 142 | $link->push( 12 ); 143 | $link->push( 3 ); 144 | $link->push( 5 ); 145 | echo 'length : '.$link->get_length().PHP_EOL; 146 | // 获取第2个位置上的数字 147 | echo "index 2 : ".$link->get_item_by_index( 2 ).PHP_EOL; 148 | // shift出第一个元素 149 | echo $link->shift().PHP_EOL; 150 | echo 'length : '.$link->get_length().PHP_EOL; 151 | echo "index 2 : ".$link->get_item_by_index( 2 ).PHP_EOL; 152 | ``` 153 | 154 | 线性表这里的基础章节就到这里结束啦,后面的文章有两个可能: 155 | - 线性表的一些实际用例 156 | - 开始二叉树相关内容 157 | 158 | --- 159 | -------------------------------------------------------------------------------- /8.数据结构之树的基本枯燥概念(一).md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elarity/data-structure-php-clanguage/ad0d770e651acdb6e5721a5fc95535a96fcc0701/8.数据结构之树的基本枯燥概念(一).md -------------------------------------------------------------------------------- /9.数据结构之树的代码实现(二).md: -------------------------------------------------------------------------------- 1 | sy的,亲自下地干活那是不可能的,这辈子都不可能的,这向来也是这个社会上大部人的常态。放到ARM处理器,也就说传说中的“一核有难,八核围观”: 2 | 3 | ![](http://static.ti-node.com/6404267478360260608) 4 | 5 | 所以说,“Talk is cheap,show me your code”。 6 | 7 | 现在我们首先考虑建立一棵二叉树: 8 | 9 | ```php 10 | data = $value; 22 | } 23 | // 添加左子树 24 | public function setLeft( Node $node ){ 25 | // 将当前class实例化后的结点作为父结点 26 | $node->parent = $this; 27 | $this->left = $node; 28 | return $node; 29 | } 30 | // 添加右子树 31 | public function setRight( Node $node ){ 32 | // 将当前class实例化后的结点作为父结点 33 | $node->parent = $this; 34 | $this->right = $node; 35 | return $node; 36 | } 37 | } 38 | ``` 39 | 我们用这段代码来实现一个“从上至下,从左往后”依次为1 -》2 -》3 -》4 -》5 -》6的二叉树: 40 | 41 | ![](http://static.ti-node.com/6404338087551303681) 42 | 43 | ```php 44 | setLeft( new Node( 2 ) ); 47 | $right = $tree->setRight( new Node( 3 ) ); 48 | $left->setLeft( new Node( 4 ) ); 49 | $left->setRight( new Node( 5 ) ); 50 | $right->setLeft( new Node( 6 ) ); 51 | print_r( $tree ); 52 | ``` 53 | 54 | 二叉树是已经建立了,紧接着就是如何遍历这颗二叉树了。遍历二叉树是指从根节点出发到按照某种顺序将树中的每一个结点都访问一次且每个结点只被访问一次。 55 | 56 | 二叉树常用的遍历方式有四种,但是,今天这里只有前三种,第四种放到后面。这前三种分别是: 57 | - 前序遍历 58 | - 中序遍历 59 | - 后序遍历 60 | 61 | 前序遍历简单说就是先走根节点,然后前序遍历左子树,最后再前序遍历右子树,拿上面代码中构建的那个二叉树为例,遍历顺序就是:1-》2-》4-》5-》3-》6。 62 | 63 | 中序遍历简单说就是从根节点开始(注意仅仅是开始,但不遍历根节点),先中序遍历左子树,然后遍历根节点,最后中序遍历右子树。拿上述二叉树为例,遍历顺序就是:4-》2-》5-》1-》6-》3。 64 | 65 | 后续遍历简单说就是先从左子树开始,然后右子树,最后根节点。按照上述二叉树为例,遍历顺序就是:4-》5-》2-》6-》3-》1。 66 | 67 | “窝跟泥杠,泥邀是能看明白丧面杠的森么,窝就扶泥!” 68 | 69 | ![](http://static.ti-node.com/6404534380059951104) 70 | 71 | 下面依然通过纸笔演练来搞定三种遍历方式,二叉树就采用程序中建立好的那个二叉树: 72 | 73 | - 前序遍历:一定记着,前序遍历的顺序是先遍历根节点,然后再前序遍历根节点的右子树,最后再前序遍历根节点的右子树!前序遍历中继续前序遍历! 74 | 75 | ![](http://static.ti-node.com/6404568204512854016) 76 | 77 | - 中序遍历:一定记着,中序遍历是先中序遍历左子树,然后遍历根节点,最后遍历右子树。 78 | 79 | ![](http://static.ti-node.com/6404568481718599681) 80 | 81 | - 后续遍历:我感觉我废话太多,不说了。 82 | 83 | ![](http://static.ti-node.com/6404568551272742912) 84 | 85 | 其实,如果泥好好演练一遍以上内容,应该能感受到一股浓重的递归风扑面而来,代码感受一下: 86 | 87 | ```php 88 | // 接着在上面的Node类中添加如下方法 89 | // 前序遍历 90 | public function preOrder( $tree ){ 91 | if( $tree instanceof Node ){ 92 | // 先遍历根节点 93 | echo $tree->data.' '; 94 | // 然后遍历左子树 95 | $this->preOrder( $tree->left ); 96 | // 最后遍历右子树 97 | $this->preOrder( $tree->right ); 98 | } 99 | } 100 | // 中序遍历 101 | public function midOrder( $tree ){ 102 | if( $tree instanceof Node ){ 103 | // 先遍历左子树 104 | $this->midOrder( $tree->left ); 105 | // 再遍历根节点 106 | echo $tree->data.' '; 107 | // 后遍历右子树 108 | $this->midOrder( $tree->right ); 109 | } 110 | } 111 | // 后续遍历 112 | public function sufOrder( $tree ){ 113 | if( $tree instanceof Node ){ 114 | // 先遍历左子树 115 | $this->sufOrder( $tree->left ); 116 | // 再遍历右子树 117 | $this->sufOrder( $tree->right ); 118 | // 最后遍历根节点 119 | echo $tree->data.' '; 120 | } 121 | } 122 | 123 | // 接着上面的运行代码,运行一下三种排序... 124 | echo $tree->preOrder( $tree ).PHP_EOL; 125 | echo $tree->midOrder( $tree ).PHP_EOL; 126 | echo $tree->sufOrder( $tree ).PHP_EOL; 127 | ``` 128 | 129 | 保存代码并运行,如下图: 130 | 131 | ![](http://static.ti-node.com/6404574548682866688) 132 | 133 | 二叉树的一些最最基础概念和操作就被我们用PHP用最粗暴的代码和方式实现了,不要骄傲,这才刚刚是个开始! 134 | 135 | ---- 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 elarity 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 微信公众号: 2 | ![](https://t.ti-node.com/static/default/img/vx_service_qrcode.jpg) 3 | 4 | 5 | ### 2019.04.10 : 被issue里一哥们给批判了,你们可以去看issue感受下,所以我用C语言来描述一下这些基础数据结构.如果有比较沙雕的代码错误,你们就issue里喷我,反正我懒得鸟你们. 6 | 7 | 8 | 第一个C语言项目,算是自己学习APUE的一个小小成果吧:https://github.com/elarity/Tiginx 9 | 10 | 11 | 2018.05.17 感谢github好友dchaofei & mangyusisha的PR和issue,修正 队列和单链表 两文种错误一共三处!向大家致歉! 12 | 13 | 14 | 正如本博客关于页所言:“ 我本淮右布衣,码农于我何加焉! ”,本泥腿子不是专业科班出身的,会存在一些缺陷。但是,本泥腿子自以为本泥腿自知自身的缺陷和软肋之所在,故不惧也。人家朱元璋身为皇帝都敢于承认自己是个泥腿子农民,我这算个啥? 15 | 16 | 遥想在2013年的时候,去辉煌国际一个小公司参与面试,遭受了些许的歧视,然而泥腿子最大的优点就是“ 不要脸 ”,歧不歧视的对本人没有任何影响。当然了,这也是本人第一次遇到这情况,截止到目前也暂时是最后一次。 17 | 18 | 其实,计算机编程的核心就是数据结构和算法,可谓是纯阳之极的内功大法,堪比《九阳神功》,张无忌呆在山沟子里多少年天天没事儿干,就练这个,除了治好了的寒冰毒外,还额外解锁“ 牛逼的内功 ”成就。所以,他后来练《乾坤大挪移》犹如神助,再后来练《太极剑》简直飞上了天,“ 我全忘了,忘得干干净净 ”可谓是装逼界的满分之作。 19 | 20 | ![](http://static.ti-node.com/6401063672902320128) 21 | 22 | 跨界的泥腿子干计算机,最大的问题就在于缺乏内功修炼导致骨质疏松,走路走不远,这玩意不好补。整个过程堪称枯燥、无味、单调、无趣、莫名其妙、不知所云、云里雾里,可谓是痛苦至极。 23 | 24 | ![](http://static.ti-node.com/6401067578973749249) 25 | 26 | 整体安排顺序如下: 27 | 28 | - 排序算法 29 | - 线性表结构 30 | - 栈结构 31 | - 队列结构 32 | - 树结构 33 | - 查找算法 34 | 35 | 送上一图,各位,自勉吧,开篇! 36 | 37 | ---- 38 | -------------------------------------------------------------------------------- /clang.binary-search.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | /* 4 | * 折半查找本质上是【有序查找】 5 | * 查找的基本前提是数据整体有序 6 | */ 7 | int main( int argc, char * argv[] ) { 8 | int target = 3; 9 | int arr[] = { 1, 3, 4, 5, 66, 666, 999, 1000, 2032, 3030, 4040, 5050, 7676 }; 10 | int arr_length = sizeof( arr ) / sizeof( int ); 11 | int left_limit_index = 0; 12 | int right_limit_index = arr_length - 1; 13 | while( left_limit_index <= right_limit_index ) { 14 | sleep( 1 ); 15 | int middle = ( left_limit_index + right_limit_index ) / 2; 16 | printf( "left:%d,right:%d,middle:%d\n", left_limit_index, right_limit_index, middle ); 17 | if ( target < arr[ middle ] ) { 18 | right_limit_index = middle - 1; 19 | } 20 | else if ( target > arr[ middle ] ) { 21 | left_limit_index = middle + 1; 22 | } 23 | else { 24 | printf( "%d bingo\n", middle ); 25 | return middle; 26 | } 27 | } 28 | printf( "没有找到\n" ); 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /clang.binary-tree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef struct binary_node_struct { 5 | int data; 6 | struct binary_node_struct * left_child; 7 | struct binary_node_struct * right_child; 8 | } binary_node_struct; 9 | 10 | // 初始化一个tree 11 | void init_binary_tree( binary_node_struct ** ); 12 | // 前序遍历 13 | void pre_order( binary_node_struct * ); 14 | // 中序遍历 15 | void in_order( binary_node_struct * ); 16 | // 后序遍历 17 | void post_order( binary_node_struct * ); 18 | // 树的深度 19 | int get_depth( binary_node_struct * ); 20 | // 叶子节点个数 21 | int get_leaf_number( binary_node_struct * ); 22 | 23 | int main( int argc, char * argv[] ) { 24 | printf( "开始初始化一个binary tree\r\n" ); 25 | printf( "输入根节点的数值:" ); 26 | binary_node_struct * t_node; 27 | init_binary_tree( &t_node ); 28 | //pre_order( t_node ); 29 | int depth = get_depth( t_node ); 30 | printf( "depth:%d\n", depth ); 31 | int leaf_number = get_leaf_number( t_node ); 32 | printf( "leaf number:%d\n", leaf_number ); 33 | return 0; 34 | } 35 | 36 | int get_leaf_number( binary_node_struct * t ) { 37 | static int number = 0; 38 | if ( t != NULL ) { 39 | if ( NULL == t->left_child && NULL == t->right_child ) { 40 | number++; 41 | } 42 | get_leaf_number( t->left_child ); 43 | get_leaf_number( t->right_child ); 44 | } 45 | return number; 46 | } 47 | 48 | int get_depth( binary_node_struct * t ) { 49 | int depth = 0; 50 | if ( NULL != t ) { 51 | // 这里...你们慢慢琢磨哈 52 | int left_child_depth = get_depth( t->left_child ); 53 | left_child_depth++; 54 | int right_child_depth = get_depth( t->right_child ); 55 | right_child_depth++; 56 | depth = left_child_depth > right_child_depth ? left_child_depth : right_child_depth ; 57 | } 58 | return depth; 59 | } 60 | 61 | void post_order( binary_node_struct * t ) { 62 | if ( NULL == t ) { 63 | return; 64 | } 65 | else { 66 | pre_order( t->left_child ); 67 | pre_order( t->right_child ); 68 | printf( "%d\n", t->data ); 69 | } 70 | } 71 | 72 | void in_order( binary_node_struct * t ) { 73 | if ( NULL == t ) { 74 | return; 75 | } 76 | else { 77 | pre_order( t->left_child ); 78 | printf( "%d\n", t->data ); 79 | pre_order( t->right_child ); 80 | } 81 | } 82 | 83 | void pre_order( binary_node_struct * t ) { 84 | if ( NULL == t ) { 85 | return; 86 | } 87 | else { 88 | printf( "%d\n", t->data ); 89 | pre_order( t->left_child ); 90 | pre_order( t->right_child ); 91 | } 92 | } 93 | 94 | /* 95 | * @desc : 初始化一个tree 96 | */ 97 | void init_binary_tree( binary_node_struct ** t ) { 98 | int data; 99 | scanf( "%d", &data ); 100 | if ( -1 == data ) { 101 | *t = NULL; 102 | return; 103 | } 104 | else { 105 | *t = ( binary_node_struct * )malloc( sizeof( binary_node_struct ) ); 106 | ( *t )->data = data; 107 | printf( "输入%d的左子树:", data ); 108 | init_binary_tree( &( ( *t )->left_child ) ); 109 | printf( "输入%d的右子树:", data ); 110 | init_binary_tree( &( ( *t )->right_child ) ); 111 | return; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /clang.hash-table.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // 用这个struct存储键值对 7 | // 比如 username:"xiaodushe" 8 | typedef struct { 9 | // 实际上个人倾向于在struct的字符串成员中不使用指针方式 10 | char * key; 11 | char * value; 12 | } hashtable_item; 13 | // 将每个键值对存储到hash-table中去 14 | typedef struct { 15 | // 当前一共有多少个键值对item 16 | int counter; 17 | // hashtable的容量有多大 18 | int size; 19 | // 知道这里为什么用二级指针么? 20 | hashtable_item ** item; 21 | } hashtable; 22 | 23 | // 这个函数用来为每个键值对 hashtable-item 分配内存 24 | // return : 指向hashtable-item的指针 25 | hashtable_item * allocate_new_item( char * key, char * item ) { 26 | hashtable_item * pt_ht_item = ( hashtable_item * )malloc( sizeof( hashtable_item ) ); 27 | // strdup函数原型位于 string.h 文件中 28 | pt_ht_item->key = strdup( key ); 29 | pt_ht_item->value = strdup( item ); 30 | return pt_ht_item; 31 | } 32 | 33 | // 为整个hash-table分配空间. 34 | // return : 指向hashtable的指针 35 | hashtable * allocate_hashtable( int size ) { 36 | hashtable * pt_ht = ( hashtable * )malloc( sizeof( hashtable ) ); 37 | pt_ht->size = size; 38 | pt_ht->counter = 0; 39 | pt_ht->item = calloc( pt_ht->size, sizeof( hashtable_item * ) ); 40 | return pt_ht; 41 | } 42 | 43 | // 释放键值对hashtable-item的内存空间 44 | void free_hashtable_item( hashtable_item * pt_item ) { 45 | // 释放清空key 和 item指针指向的空间 46 | free( pt_item->key ); 47 | free( pt_item->value ); 48 | // 最后再释放item自己 49 | // 如果key和item并不是指针方式的话,就只需要free item即可 50 | free( pt_item ); 51 | } 52 | 53 | // 释放整个hashtable 54 | void free_hashtable( hashtable * pt_ht ) { 55 | // 释放掉hashtable中所有的hashtable-item键值对 56 | for( int i = 1; i < pt_ht->size; i++ ) { 57 | hashtable_item * pt_ht_item = pt_ht->item[ i ]; 58 | // 如果不为空指针,说明该位置是有hashtable-item键值对 59 | if( NULL != pt_ht_item ) { 60 | free_hashtable_item( pt_ht_item ); 61 | } 62 | } 63 | // 清空item成员指针指向的内存空间 64 | free( pt_ht->item ); 65 | // 清空hashtable自己本身 66 | free( pt_ht ); 67 | } 68 | 69 | // 网上广为流传的time33 hash算法...反正能用 70 | uint time33( char * str, const int size ) { 71 | uint hash = 5381; 72 | while( *str ){ 73 | hash += ( hash << 5 ) + ( *str++ ); 74 | } 75 | hash = ( hash & 0x7FFFFFFF ); 76 | // 此处沙雕地改了一下,凑合看...正式用千万别这么搞,可能会被人打死 77 | hash = hash % size; 78 | return hash; 79 | } 80 | 81 | // 插入数据 82 | // param : hashtable_item * 83 | void insert_item_hashtable( const char * key, const char * item, hashtable * pt_hashtable ) { 84 | // 检测hashtable容量是否已经满了 85 | if ( pt_hashtable->counter == pt_hashtable->size ) { 86 | printf( "hashtable容量已经满了.\n" ); 87 | return; 88 | } 89 | hashtable_item * pt_hashtable_item = allocate_new_item( key, item ); 90 | // 计算这个item键值对在hashtable中的位置。 91 | uint index = time33( pt_hashtable_item->key, pt_hashtable->size ); 92 | //printf( "%u\n", index ); 93 | // 检测index位置上是否已经有元素 94 | // 如果有元素,则查找下一个元素位置 95 | // item键值对的指针就存在这里:pt_hashtable->item 96 | // 偏移量从0开始 97 | // 如果当前位置是空的,直接插入元素即可. 98 | if ( NULL == pt_hashtable->item[ index ] ) { 99 | pt_hashtable->item[ index ] = pt_hashtable_item; 100 | pt_hashtable->counter++; 101 | } 102 | // 如果当前有元素,也就是哈希碰撞了,用粗暴的开放寻址法解决一下... 103 | else { 104 | } 105 | } 106 | 107 | // 获取这个数据... 108 | void get_item_hashtable( ) { 109 | } 110 | 111 | // 删除数据 112 | void delete_item_hashtable() { 113 | } 114 | 115 | int main( int argc, char * argv[] ) { 116 | // 初始化一个hashtable,容量为可以存储20个item键值对 117 | int size = 20; 118 | hashtable * pt_hashtable = allocate_hashtable( size ); 119 | // 初始化一个item键值对. 120 | char * key = "username"; 121 | char * item = "xiaodushe"; 122 | // 将item键值对插入到hashtable中去 123 | insert_item_hashtable( key, item, pt_hashtable ); 124 | // 去hashtable获取这个item键值对 125 | 126 | // 清空这个hashtable-item 127 | //free_hashtable_item( pt_ht_item ); 128 | // 清空这个hashtable 129 | free_hashtable( pt_hashtable ); 130 | } 131 | -------------------------------------------------------------------------------- /clang.linear-list.c: -------------------------------------------------------------------------------- 1 | #include 2 | #define TRUE 1 3 | #define FALSE 0 4 | #define ERROR -1 5 | #define SQ_SIZE 6 6 | 7 | typedef struct seq_list { 8 | int element_array[ SQ_SIZE ]; 9 | int length; 10 | } seq_list; 11 | 12 | int create_element( int, int, seq_list * ); 13 | int update_element( int, int, seq_list * ); 14 | int read_element( int, seq_list * ); 15 | int delete_element( int, seq_list * ); 16 | int get_sq_length( seq_list * ); 17 | void range_sq( seq_list * ); 18 | 19 | int main( void ) { 20 | // 声明一个线性表. 21 | seq_list sq; 22 | sq.length = 0; 23 | create_element( 1, 1, &sq ); 24 | create_element( 2, 2, &sq ); 25 | create_element( 3, 3, &sq ); 26 | create_element( 4, 4, &sq ); 27 | create_element( 5, 5, &sq ); 28 | create_element( 6, 6, &sq ); 29 | range_sq( &sq ); 30 | int sq_length = get_sq_length( &sq ); 31 | printf( "sq length : %d\n", sq_length ); 32 | // 寻找一个节点 33 | int element = read_element( 3, &sq ); 34 | printf( "ele in 3 : %d\n", element ); 35 | // 更新一个节点 36 | update_element( 3, 33, &sq ); 37 | range_sq( &sq ); 38 | // 删除一个节点 39 | delete_element( 3, &sq ); 40 | range_sq( &sq ); 41 | // 删除一个节点 42 | delete_element( 3, &sq ); 43 | range_sq( &sq ); 44 | // 添加一个元素 45 | create_element( 1, 11, &sq ); 46 | range_sq( &sq ); 47 | return 0; 48 | } 49 | 50 | void range_sq( seq_list * sq ) { 51 | int array_right = sq->length - 1; 52 | for ( int index = 0; index <= array_right; index++ ) { 53 | printf( "%d,", sq->element_array[ index ] ); 54 | } 55 | printf( "\n" ); 56 | } 57 | 58 | /* 59 | @desc : 删除元素 60 | */ 61 | int delete_element( int index, seq_list * sq ) { 62 | if ( index < 1 || index > SQ_SIZE ) { 63 | return ERROR; 64 | } 65 | if ( 0 == sq->length ) { 66 | return ERROR; 67 | } 68 | int array_index = index - 1; 69 | // 线性表的右侧边界 70 | int array_right = sq->length - 1; 71 | // 如果删除的是最后一个元素,那就省点儿力气 72 | if ( array_index == ( sq->length - 1 ) ) { 73 | sq->length--; 74 | return TRUE; 75 | } else { 76 | // 如果删除的元素不是最后一个,恶心开始到家了 77 | // 从当前位置开始一直到最后一个元素,统统往前挪一位 78 | for ( ; array_index < array_right; array_index++ ) { 79 | int temp = sq->element_array[ array_index + 1 ]; 80 | sq->element_array[ array_index ] = temp; 81 | } 82 | sq->length--; 83 | return TRUE; 84 | } 85 | } 86 | 87 | int update_element( int index, int element, seq_list * sq ) { 88 | if ( index < 1 || index > sq->length ) { 89 | return ERROR; 90 | } 91 | int array_index = index - 1; 92 | sq->element_array[ array_index ] = element; 93 | return TRUE; 94 | } 95 | 96 | /* 97 | @desc : index依然为自然位置长度 98 | element就是你要打算搞个啥值进去 99 | sq为线性表元素指针类型变量 100 | */ 101 | int create_element( int index, int element, seq_list * sq ) { 102 | if ( index > SQ_SIZE || index < 0 ) { 103 | return ERROR; 104 | } 105 | // 如果线性表已经满了,错误 106 | if ( SQ_SIZE == sq->length ) { 107 | return ERROR; 108 | } 109 | int array_index = index - 1; 110 | if ( 0 == sq->length ) { 111 | sq->element_array[ array_index ] = element; 112 | sq->length++; 113 | } else { 114 | // 线性表当前最右侧元素位置 115 | int array_right = sq->length - 1; 116 | // 最恶心来了! 117 | for ( ; array_right >= array_index; array_right-- ) { 118 | int temp = sq->element_array[ array_right ]; 119 | sq->element_array[ array_right + 1 ] = temp; 120 | } 121 | // 知道这里如何把 添加元素和长度加一 做成原子性操作吗? 122 | sq->element_array[ array_index ] = element; 123 | sq->length++; 124 | } 125 | return TRUE; 126 | } 127 | 128 | /* 129 | @desc : index为自然位置, 130 | 不指数组中从0开始的那种反人类位置 131 | 1就是第一位,不接受杠精反驳 132 | 谁杠打死谁 133 | */ 134 | int read_element( int index, seq_list * sq ) { 135 | // 位置不能超过线性表的长度,也不能小于1 136 | if ( index > SQ_SIZE || index < 1 ) { 137 | return ERROR; 138 | } 139 | // 转换为数组位置 140 | int ret; 141 | int array_index; 142 | array_index = index - 1; 143 | // sq为线性表的指针类型变量,所以用-> 144 | ret = sq->element_array[ array_index ]; 145 | return ret; 146 | } 147 | 148 | int get_sq_length( seq_list * sq ) { 149 | return sq->length; 150 | } 151 | -------------------------------------------------------------------------------- /clang.linear-search.for-version.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main( int argc, char * argv[] ) { 4 | int target = 938; 5 | int arr_index; 6 | int arr_length; 7 | int arr[] = { 11, 234, 12, 44, 4534, 134, 94, 938, 13342, 87762 }; 8 | arr_length = sizeof( arr ) / sizeof( int ); 9 | for( int i = 0; i < arr_length; i++ ) { 10 | if ( target == arr[ i ] ) { 11 | arr_index = i; 12 | break; 13 | } 14 | } 15 | printf( "bingo : %d\n", arr_index ); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /clang.linear-search.while-version.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main( int argc, char * argv[] ) { 4 | int arr_index; 5 | int target = 9381; 6 | int arr[] = { [1]=11, 234, 12, 44, 4534, 134, 94, 938, 13342, 87762 }; 7 | arr[ 0 ] = target; 8 | int arr_length = sizeof( arr ) / sizeof( int ); 9 | int arr_right_limit = arr_length - 1; 10 | while( arr[ arr_right_limit ] ) { 11 | //printf( "%d\n", arr[ arr_right_limit ] ); 12 | if ( target == arr[ arr_right_limit ] ) { 13 | arr_index = arr_right_limit; 14 | break; 15 | } 16 | arr_right_limit--; 17 | } 18 | //printf( "bingo : %d\n", arr_index ); 19 | if ( 0 == arr_index ) { 20 | printf( "没有找到\n" ); 21 | } 22 | else { 23 | printf( "找到了,位置为%d\n", arr_index ); 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /clang.link-queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* 5 | * @desc : 队列的链式存储实现方案,两个指针:头指针指向头节点,头节点指向队列第一个节点;尾巴指针 6 | 指向最后队列最后一个节点 7 | 8 | PS:头节点不是队列的第一个节点! 9 | 10 | -------- --------- --------- --------- 11 | 头指针--->|头节点|--->|第一节点|--->|第二节点|--->|尾巴节点| <---尾巴指针 12 | -------- --------- --------- --------- 13 | 14 | 当队列为空的时候,头指针和尾巴指针都指向 头节点 ~~ 15 | 16 | */ 17 | 18 | typedef struct queue_node { 19 | int element; 20 | struct queue_node * next; 21 | } queue_node; 22 | typedef queue_node * queue_node_p; 23 | 24 | typedef struct queue { 25 | queue_node_p head; 26 | queue_node_p tail; 27 | } queue; 28 | typedef queue * queue_p; 29 | 30 | void add( queue_p, int ); 31 | int del( queue_p ); 32 | 33 | int main( int argc, char * argv[] ) { 34 | // 初始化一个空队列 35 | queue_p q = ( queue_p )malloc( sizeof( queue ) ); 36 | // 初始化一个头节点 37 | queue_node_p head_node = ( queue_node_p )malloc( sizeof( queue_node ) ); 38 | head_node->element = 0; 39 | head_node->next = NULL; 40 | q->head = head_node; 41 | q->tail = head_node; 42 | // 43 | add( q, 1 ); 44 | add( q, 2 ); 45 | add( q, 3 ); 46 | add( q, 4 ); 47 | // 48 | int temp = del( q ); 49 | printf( "%d\n", temp ); 50 | temp = del( q ); 51 | printf( "%d\n", temp ); 52 | temp = del( q ); 53 | printf( "%d\n", temp ); 54 | temp = del( q ); 55 | printf( "%d\n", temp ); 56 | temp = del( q ); 57 | printf( "%d\n", temp ); 58 | return 0; 59 | } 60 | 61 | void add( queue_p q, int element ) { 62 | // 开辟新节点 63 | queue_node_p node = ( queue_node_p )malloc( sizeof( queue_node ) ); 64 | node->element = element; 65 | node->next = NULL; 66 | q->tail->next = node; 67 | q->tail = node; 68 | } 69 | 70 | int del( queue_p q ) { 71 | if ( q->head == q->tail ) { 72 | printf( "队列是空的\n" ); 73 | exit( -1 ); 74 | } 75 | int temp; 76 | temp = ( *( q->head->next ) ).element; 77 | q->head = q->head->next; 78 | free( q->head ); 79 | return temp; 80 | } 81 | -------------------------------------------------------------------------------- /clang.link-stack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | /* 4 | * @desc : 链式存储实现堆栈比较屌的一个地方就是不用关心容量问题了。主要内存够用,你就卯着劲malloc 5 | 又不是不能用。每个节点由“数据和指向下一个节点的指针”组成! 6 | */ 7 | 8 | // 这个就是每个节点的struct结构 9 | typedef struct link_stack_node { 10 | int element; 11 | struct link_stack_node * next; 12 | } link_stack_node; 13 | typedef link_stack_node * link_stack_node_p; 14 | 15 | // 这个栈的struct结构 16 | // top_pointer是栈顶指针,!!!注意!!!如果是空栈,该指针内容为NULL即可! 17 | // length是栈中元素个数 18 | typedef struct link_stack { 19 | link_stack_node_p top_pointer; 20 | int length; 21 | } link_stack; 22 | typedef link_stack * link_stack_p; 23 | 24 | // 依然还要重点实现push和pop 25 | void push( link_stack_p, int ); 26 | int pop( link_stack_p ); 27 | 28 | int main( int argc, char * argv[] ) { 29 | // malloc分配栈信息结构 30 | link_stack_p s = ( link_stack_p )malloc( sizeof( link_stack ) ); 31 | s->top_pointer = NULL; 32 | s->length = 0; 33 | // push一个元素 34 | push( s, 1 ); 35 | push( s, 2 ); 36 | push( s, 19 ); 37 | push( s, 29 ); 38 | printf( "length : %d\n", s->length ); 39 | // pop 一个元素 40 | int temp = pop( s ); 41 | printf( "pop : %d\n", temp ); 42 | printf( "length : %d\n", s->length ); 43 | return 0; 44 | } 45 | 46 | void push( link_stack_p s, int element ) { 47 | // 注意!此时push就不需要判断栈容量是否够或者栈是否满了,你自己看下你内存,自己看着办 48 | // malloc一个新节点 49 | link_stack_node_p s_node = ( link_stack_node_p )malloc( sizeof( link_stack_node ) ); 50 | s_node->element = element; 51 | s_node->next = s->top_pointer; 52 | // 将栈s的 元素数量加1并将栈顶指针指向该新节点 53 | s->length++; 54 | s->top_pointer = s_node; 55 | } 56 | 57 | int pop( link_stack_p s ) { 58 | // 这个。。。好像得判断下是不是空栈,是吧... 59 | if ( NULL == s->top_pointer ) { 60 | printf( "空栈\n" ); 61 | exit( -1 ); 62 | } 63 | // 将栈顶指针指向 次栈顶 节点! 64 | s->length--; 65 | s->top_pointer = s->top_pointer->next; 66 | // 然后。。开始pop,这个由于节点的内存都是通过malloc过来的,所以,pop后要free下 67 | // 不然内存就炸裂了 68 | int temp; 69 | // 我比较记不住 *,->,. 的优先级,我比较怂,不整高端的,直接加括弧表明优先 70 | // 希望编译器比较屌,能优化了.. 71 | temp = ( *( s->top_pointer ) ).element; 72 | // free内存 并将长度减1 73 | free( s->top_pointer ); 74 | return temp; 75 | } 76 | -------------------------------------------------------------------------------- /clang.seq-cycle-queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define SIZE 5 4 | 5 | /* 6 | * @desc : 顺序结构存储的队列。队列就是那种队头出,队尾进的玩意,跟地铁安检一样: 7 | 先来先检往前走,后来后排往前走. 8 | 大概如下图: 9 | ----------- 10 | <- |0|1|2|3|4| <- 11 | ----------- 12 | 下面我们说的这个队列是顺序存储方式实现队列中比较合理的一种队列:循环队列...意思大概就是如果你的队列是 13 | 基于顺序存储实现的,那么搞成循环队列是可以满足你绝大多数场景的,别的都是辣鸡 14 | 1,队尾(yi)巴上来一个很容易操作,直接尾巴上怼个元素就行 15 | 2,头上出一个就比较恶心了,比如0出去后,后面1 2 3 4都要分别往前挪一位,你要用for循环操作么?多恶心... 16 | 3,所以为了避免2中沙雕移动操作,索性就懒惰一点儿吧,0出去就出去了,后面一个元素都不用动 17 | 4,但是真的如果如3所做了。。。也比较恶心,比如极端的情况,0 1 2 3都滚蛋出去了,5也不动,再来新的,就可能要GG 18 | 5,当然了,我可是看到了昂,前面4个位置都是空的,但是新来的就是没地儿放,这就是传说中的“假溢出”... 19 | 6,那按理说,想办法把新来的往前面的空坑里怼就行了吧。。。 20 | 7,所以,不知道哪个大佬又想了一个办法,就是弄俩指针,一个指头,一个指尾 21 | 22 | ----------- 23 | | | |2|3|4| 24 | ----------- 25 | | | 26 | t h 27 | 28 | 假设头指针叫head,尾指针叫做tail。当队列如上图所示时候,head指向2,tail指向第一个空坑(也就是原来0的位置) 29 | 如果此时新来一个5,就把5往0的位置怼,tail位置往后挪到原来1的位置; 30 | ----------- 31 | |5| |2|3|4| 32 | ----------- 33 | | | 34 | t h 35 | 如果出队列,就是从head指向的2中出。 36 | 然后妈蛋又来一个6,此时tail指向原来1的位置,head依然指向2,6来后占了原来1的位置,tail指针指向2,此时head和tail指向同一个位置! 37 | ----------- 38 | |5|6|2|3|4| 39 | ----------- 40 | || 41 | th 42 | 这会儿最大的问题就是:如果开始队列开始出数据,一直出一直爽一直出完都没有进来的,head最终也将落到2的位置,head和tail 43 | 的位置又是相同的。 44 | ----------- 45 | | | | | | | 46 | ----------- 47 | || 48 | th 49 | ” 队列满着的时候,head=tail;队列空的时候,head=tail “ 50 | ?????? 51 | 到底是空着的还是满着的? 52 | 这个其实上是有很多解决方案的,不要把思维限制死了: 53 | 1·比如你额外搞个变量去记录当前队列有几个元素,通过占据一个空间来解决这个问题 54 | 2·还有一个办法是让tail指针指向的尾巴始终为空,就是说虽然数组SIZE=5,但是当存了4个元素时,计算满了,这样head和tail 55 | 绝对不会重合,位置绝对值为1;而队列空的时候呢,就让head=tail,你们自己琢磨琢磨;这个是可以实现的。 56 | 假如我们就用第二个办法. 57 | 1,队列为空时,tail=head 58 | 2,队列满着的时候:( tail + 1 )%5 = head ,(5是队列长度)别问我为什么,你自己算下然后背过,人类就是在复读和背诵中进化的 59 | 3,队列长度: ( tail - head + 5 )%5 ,别问我为什么。。。爱背不背,要不然你就用方法1去实现 60 | */ 61 | 62 | typedef struct seq_queue { 63 | int element[ SIZE ]; 64 | int head; 65 | int tail; 66 | } seq_queue; 67 | typedef seq_queue * seq_queue_q; 68 | 69 | // 添加元素 70 | void add_to_q( seq_queue_q, int ); 71 | // 删除元素 72 | int del_from_q( seq_queue_q ); 73 | // 获取队列长度 74 | int get_length( seq_queue_q ); 75 | 76 | int main( int argc, char * argv[] ) { 77 | int length; 78 | // 初始化一个空队列吧 79 | // 根据此前定义,当head=tail时,就是空队列 80 | seq_queue_q q = ( seq_queue_q )malloc( sizeof( seq_queue ) ); 81 | q->head = 0; 82 | q->tail = 0; 83 | 84 | // 获取长度 85 | length = get_length( q ); 86 | printf( "length : %d\n", length ); 87 | 88 | // 入列 89 | add_to_q( q, 1 ); 90 | add_to_q( q, 2 ); 91 | add_to_q( q, 3 ); 92 | 93 | // 获取长度 94 | length = get_length( q ); 95 | printf( "length : %d\n", length ); 96 | 97 | // 出列 98 | int temp = del_from_q( q ); 99 | printf( "del : %d\n", temp ); 100 | length = get_length( q ); 101 | printf( "length : %d\n", length ); 102 | 103 | return 0; 104 | } 105 | 106 | void add_to_q( seq_queue_q q, int element ) { 107 | // 先判断队列是不是满了 108 | if ( q->head == ( q->tail + 1 ) % SIZE ) { 109 | printf( "队列满着呢\n" ); 110 | exit( -1 ); 111 | } 112 | q->element[ q->tail ] = element; 113 | q->tail = ( q->tail + 1 ) % SIZE; 114 | } 115 | 116 | int del_from_q( seq_queue_q q ) { 117 | // 判断队列是不是空的 118 | if ( q->head == q->tail ) { 119 | printf( "队列空着呢\n" ); 120 | exit( -1 ); 121 | } 122 | int temp = q->element[ q->head ]; 123 | q->head = ( q->head + 1 ) % SIZE; 124 | return temp; 125 | } 126 | 127 | int get_length( seq_queue_q q ) { 128 | printf( "%d %d\n", q->tail, q->head); 129 | return ( q->tail - q->head + SIZE ) % SIZE; 130 | } 131 | -------------------------------------------------------------------------------- /clang.seq-stack.c: -------------------------------------------------------------------------------- 1 | #include 2 | /* @desc : exit原型在stdlib.h中 */ 3 | #include 4 | #define SIZE 5 5 | 6 | /* 7 | * @desc : 顺序存储实现栈,使用数组是很自然的 8 | * top-pointer则是表明当前栈的位置 9 | -1表示空栈,0表示有一个元素 10 | */ 11 | typedef struct stack { 12 | int element[ SIZE ]; 13 | int top_pointer; 14 | } stack; 15 | // 下面这句你可以这么理解: 令( stack * ) = stack_pointer 16 | typedef stack * stack_pointer; 17 | 18 | /* 19 | * @desc : 一般说来,栈有两个重要操作需要实现,一个是push,一个是pop 20 | */ 21 | void push( stack_pointer, int ); 22 | int pop( stack_pointer ); 23 | 24 | int main( int argc, char * argv[] ) { 25 | // 给栈结构体分配一个内存吧 26 | stack_pointer s = ( stack_pointer )malloc( sizeof( stack_pointer ) ); 27 | // 记住了哈,初始化空栈,-1时为空,当然这个不是定死的,只是本案例中-1代表空 28 | s->top_pointer = -1; 29 | // 入栈 30 | push( s, 1 ); 31 | push( s, 2 ); 32 | push( s, 3 ); 33 | push( s, 4 ); 34 | push( s, 5 ); 35 | printf( "栈顶为:%d\n", s->top_pointer ); 36 | // 出栈 37 | int temp = pop( s ); 38 | printf( "栈顶为:%d , 弹出元素为:%d\n", s->top_pointer, temp ); 39 | temp = pop( s ); 40 | printf( "栈顶为:%d , 弹出元素为:%d\n", s->top_pointer, temp ); 41 | temp = pop( s ); 42 | printf( "栈顶为:%d , 弹出元素为:%d\n", s->top_pointer, temp ); 43 | temp = pop( s ); 44 | printf( "栈顶为:%d , 弹出元素为:%d\n", s->top_pointer, temp ); 45 | temp = pop( s ); 46 | printf( "栈顶为:%d , 弹出元素为:%d\n", s->top_pointer, temp ); 47 | temp = pop( s ); 48 | printf( "栈顶为:%d , 弹出元素为:%d\n", s->top_pointer, temp ); 49 | return 0; 50 | } 51 | 52 | /* 53 | * @param : s ,栈的指针 54 | * @param : element , 要入栈的元素 55 | */ 56 | void push( stack_pointer s, int element ) { 57 | // 判断一下栈是不是满了 · 58 | if ( SIZE - 1 == s->top_pointer ) { 59 | printf( "stack满了\n" ); 60 | exit( -1 ); 61 | } 62 | s->element[ ++( s->top_pointer ) ] = element; 63 | } 64 | 65 | /* 66 | * @desc : s 栈的指针 67 | */ 68 | int pop( stack_pointer s ) { 69 | if ( -1 == s->top_pointer ) { 70 | printf( "stack是空的\n" ); 71 | exit( -1 ); 72 | } 73 | int temp = s->element[ s->top_pointer ]; 74 | s->element[ s->top_pointer ] = 0; 75 | s->top_pointer--; 76 | return temp; 77 | } 78 | -------------------------------------------------------------------------------- /clang.single-link-list.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define TRUE 1 5 | #define FALSE 0 6 | #define ERROR -1 7 | 8 | typedef struct node { 9 | int element; 10 | struct node * next; 11 | } node; 12 | //有些童鞋有疑问:下面这句的意思就是将"node *"定义为快捷类型link_list,以后link_list就是node类型指针变量 13 | typedef node * link_list; 14 | 15 | /* 16 | * @desc : 世间一切皆为增删改查!你看看我函数命名方式就是curd!多么的接地气 17 | */ 18 | int create_node( link_list, int, int ); 19 | int update_node( link_list, int, int ); 20 | int read_node( link_list, int ); 21 | int delete_node( link_list, int ); 22 | int range_link_list(); 23 | void destroy_link_list(); 24 | 25 | int main( void ) { 26 | // 初始化单链表头节点 27 | link_list head, first; 28 | head = ( link_list )malloc( sizeof( link_list ) ); 29 | head->element = 0; 30 | head->next = NULL; 31 | // 初始化单链条第一个节点 32 | first = ( link_list )malloc( sizeof( link_list ) ); 33 | first->element = 1; 34 | first->next = NULL; 35 | head->next = first; 36 | // 创建一个节点 37 | create_node( head, 1, 2 ); 38 | create_node( head, 1, 3 ); 39 | create_node( head, 1, 4 ); 40 | create_node( head, 1, 5 ); 41 | // 读取一个节点 42 | int temp = read_node( head, 1 ); 43 | printf( "%d\n", temp ); 44 | temp = read_node( head, 2 ); 45 | printf( "%d\n", temp ); 46 | temp = read_node( head, 3 ); 47 | printf( "%d\n", temp ); 48 | temp = read_node( head, 4 ); 49 | printf( "%d\n", temp ); 50 | temp = read_node( head, 5 ); 51 | printf( "%d\n", temp ); 52 | printf( "===================\n" ); 53 | // 更新一个节点 54 | update_node( head, 1, 11 ); 55 | update_node( head, 2, 22 ); 56 | temp = read_node( head, 1 ); 57 | printf( "%d\n", temp ); 58 | temp = read_node( head, 2 ); 59 | printf( "%d\n", temp ); 60 | temp = read_node( head, 3 ); 61 | printf( "%d\n", temp ); 62 | temp = read_node( head, 4 ); 63 | printf( "%d\n", temp ); 64 | temp = read_node( head, 5 ); 65 | printf( "%d\n", temp ); 66 | printf( "===================\n" ); 67 | // 删除一个节点 68 | delete_node( head, 2 ); 69 | temp = read_node( head, 1 ); 70 | printf( "%d\n", temp ); 71 | temp = read_node( head, 2 ); 72 | printf( "%d\n", temp ); 73 | temp = read_node( head, 3 ); 74 | printf( "%d\n", temp ); 75 | temp = read_node( head, 4 ); 76 | printf( "%d\n", temp ); 77 | return 0; 78 | } 79 | 80 | /* 81 | * @desc : 向某个位置插入一个元素 82 | * @param : 头指针 83 | @param : index是位置 84 | @parma : element是要节点的元素内容 85 | */ 86 | int create_node( link_list node, int index, int element ) { 87 | if ( index < 1 ) { 88 | return ERROR; 89 | } 90 | int counter = 1; 91 | while ( node && counter < index ) { 92 | node = node->next; 93 | counter++; 94 | } 95 | // 判断index位置是否存在或合法 96 | if ( NULL == node || counter > index ) { 97 | printf( "位置不存在\n" ); 98 | return ERROR; 99 | } 100 | // 此时的ll,就是index位置上的节点了 101 | // 证明一个指向node的指针类型变量 102 | // 并为之分配内存 103 | // 然后往内存中存储数据 104 | link_list new_node; 105 | new_node = ( link_list )malloc( sizeof( link_list ) ); 106 | new_node->element = element; 107 | new_node->next = node->next; 108 | node->next = new_node; 109 | // 创建新的节点 110 | return TRUE; 111 | } 112 | 113 | /* 114 | * @desc : 更新某个位置上元素 115 | */ 116 | int update_node( link_list node, int index, int element ) { 117 | // 位置不能小于第一个吧 118 | if ( index < 1 ) { 119 | return ERROR; 120 | } 121 | node = node->next; 122 | int counter = 1; 123 | while ( node && counter < index ) { 124 | node = node->next; 125 | counter++; 126 | } 127 | // 位置超过了最后一个了,也就是index位置不存在节点了 128 | if ( NULL == node ) { 129 | return ERROR; 130 | } 131 | // 执行更新操作 132 | node->element = element; 133 | return TRUE; 134 | } 135 | 136 | /* 137 | * @desc : 读取链表某个位置上的元素 138 | * @param : index表示位置,自然长度,不是数组那种傻屌似的0表示第一个,1就是第一个 139 | * @param : ll就是链表的入口节点 140 | */ 141 | int read_node( link_list node, int index ) { 142 | // 位置不能小于第一个吧 143 | if ( index < 1 ) { 144 | return ERROR; 145 | } 146 | node = node->next; 147 | int counter = 1; 148 | while ( node && counter < index ) { 149 | node = node->next; 150 | counter++; 151 | } 152 | // 位置超过了最后一个了,也就是index位置不存在节点了 153 | if ( NULL == node ) { 154 | return ERROR; 155 | } 156 | // 此时的ll表示index位置的节点 157 | return node->element; 158 | } 159 | 160 | int delete_node( link_list node, int index ) { 161 | // 位置不能小于第一个吧 162 | if ( index < 1 ) { 163 | return ERROR; 164 | } 165 | //node = node->next; 166 | int counter = 1; 167 | while ( node && counter < index ) { 168 | node = node->next; 169 | counter++; 170 | } 171 | // 位置超过了最后一个了,也就是index位置不存在节点了 172 | if ( NULL == node ) { 173 | return ERROR; 174 | } 175 | link_list temp = node->next; 176 | node->next = node->next->next; 177 | free( temp ); 178 | return TRUE; 179 | } 180 | -------------------------------------------------------------------------------- /clang.two-seq-stack-share-mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define SIZE 5 4 | 5 | /* 6 | * @desc : 两stack共享空间的demo 7 | 就是两个stack的顶对着顶,当栈1满了后,可以向栈2中存数据 8 | 我在注释里画个图简单表示一下 9 | 10 | -1 0 1 2 3 4 5 6 7 8 9 10 11 | ----------- ----------- 12 | 栈1 | | | | | | | | | | | | 栈2 13 | ----------- ----------- 14 | 栈1的栈顶方向是从左往右的,栈2的栈顶方向是从右往左的 15 | 我们暂且给这种栈起个名字叫做“双栈”,往双栈里push元素的时候,是从栈 16 | 两侧向中间方向前进的。也就是说往栈1里push,就是从左右往右;往栈2 17 | 里push,就是从右往左 18 | 双栈什么时候满栈呢?就是 栈1的栈顶位置和栈2的栈顶位置差1的时候 19 | */ 20 | typedef struct double_stack { 21 | int element[ SIZE ]; 22 | // 栈1的栈顶位置 23 | int pre_stack_top; 24 | // 栈2的栈顶位置 25 | int suf_stack_top; 26 | } double_stack; 27 | // 再说最后一遍:double_stack * = double_stack_p 28 | typedef double_stack * double_stack_p; 29 | 30 | // 依然还是要实现两个功能,push和pop 31 | // 不过,由于是两个栈的栈顶对栈顶,所以,参数需要表明是操作那个栈 32 | void push( double_stack_p, int, int ); 33 | int pop( double_stack_p, int ); 34 | 35 | int main( int argc, char * argv[] ) { 36 | // 初始化一个双栈 37 | double_stack_p s = ( double_stack_p )malloc( sizeof( double_stack ) ); 38 | // -1表示栈1是空栈 39 | s->pre_stack_top = -1; 40 | // 10表示栈2是空栈 41 | s->suf_stack_top = 10; 42 | // push内容 43 | push( s, 1, 1 ); 44 | push( s, 2, 1 ); 45 | push( s, 10, 2 ); 46 | printf( "栈1顶部:%d,栈2顶部:%d\n", s->pre_stack_top, s->suf_stack_top ); 47 | push( s, 3, 1 ); 48 | printf( "栈1顶部:%d,栈2顶部:%d\n", s->pre_stack_top, s->suf_stack_top ); 49 | push( s, 9, 2 ); 50 | printf( "栈1顶部:%d,栈2顶部:%d\n", s->pre_stack_top, s->suf_stack_top ); 51 | // pop 52 | int temp = pop( s, 1 ); 53 | printf( "pop : %d\n", temp ); 54 | temp = pop( s, 2 ); 55 | printf( "pop : %d\n", temp ); 56 | printf( "栈1顶部:%d,栈2顶部:%d\n", s->pre_stack_top, s->suf_stack_top ); 57 | return 0; 58 | } 59 | 60 | /* 61 | * @param : s 双栈结构体指针 62 | * @param : element 要push的元素 63 | * @param : stack_flag 栈的标记 1表示栈1,2表示栈2 64 | */ 65 | void push( double_stack_p s, int element, int stack_flag ) { 66 | // 先判断是否栈满了 67 | if ( s->suf_stack_top - s->pre_stack_top == 1 ) { 68 | printf( "栈满了\n" ); 69 | exit( -1 ); 70 | } 71 | // 栈1 72 | if ( 1 == stack_flag ) { 73 | s->element[ ++s->pre_stack_top ] = element; 74 | } 75 | // 栈2 76 | else if ( 2 == stack_flag ) { 77 | s->element[ --s->suf_stack_top ] = element; 78 | } 79 | else { 80 | printf( "错误栈号\n" ); 81 | exit( -1 ); 82 | } 83 | } 84 | 85 | /* 86 | * @param : s 双栈结构体指针 87 | */ 88 | int pop( double_stack_p s, int stack_flag ) { 89 | int temp; 90 | if ( 1 == stack_flag ) { 91 | if ( -1 == s->pre_stack_top ) { 92 | printf( "栈1是空的\n" ); 93 | exit( -1 ); 94 | } 95 | temp = s->element[ s->pre_stack_top ]; 96 | s->element[ s->pre_stack_top ] = 0; 97 | s->pre_stack_top--; 98 | } 99 | else if ( 2 == stack_flag ) { 100 | if ( 10 == s->suf_stack_top ) { 101 | printf( "栈2是空的\n" ); 102 | exit( -1 ); 103 | } 104 | temp = s->element[ s->suf_stack_top ]; 105 | s->element[ s->suf_stack_top ] = 0; 106 | s->suf_stack_top++; 107 | } 108 | return temp; 109 | } 110 | --------------------------------------------------------------------------------