关于排序:选择排序

<article class=“article fmt article-content”><h2>抉择排序</h2><h4>目录</h4><ul><li>1.算法思维</li><li>2.从[算法思维]到[代码实现]</li><li>3.代码实现(C、Python)</li></ul><h3>1. 算法思维</h3><blockquote>抉择排序:每一轮抉择出 <code>以后未排序区间中最小的元素</code>放到 <code>对应的地位</code>上</blockquote><p>排序过程:</p><p>数组a有n个元素</p><ul><li>第一轮:a[0]-a[n-1]为待排序区间,在此区间中选出最小的元素放在a[0]上;</li><li>第二轮:a[1]-a[n-1]是新的待排序区间,在此区间中选出最小的元素放到a[1]上;</li><li><p>第三轮:a[2]-a[n-1]是新的待排序区间,在此区间中选出最小的元素放到a[2]上;</p><p>….</p></li><li>第 n-1 轮:通过下面的排序,从a[0]-a[n-2]顺次放入了最小、次小..的元素,a[0]-a[n-2]为有序区间,此时待排序区间只剩下一个元素即最初一个元素,无需再进行排序,整个数组排序结束。</li></ul><h3>2.从“算法思维”到“代码实现”</h3><p>察看上述算法中的排序过程</p><ol><li>共须要n-1轮排序,每一轮排序外部的操作都一样,因而能够用for循环来实现每一轮排序。循环变量 i 的取值范畴为[0,n-1]</li><li>在每一轮排序外部为了最终找出剩下区间中最小的元素,须要遍历整个剩下的区间。因而每一轮排序外部也有一个for循环。循环变量 j 的取值范畴为[i+1,n-1]</li></ol><p>综上所述,算法能够应用2层for循环来实现</p><ul><li>外层的循环次数是排序轮次,有n个元素,则循环n-1次;这个for循环实现每一轮排序</li><li><p>内层for循环实现在每一轮排序外部找出剩下区间中的最小元素</p><p>双层for循环示意图:<br/><br/></p></li></ul><h3>3. 代码实现(C、Python)</h3><p>C语言实现</p><pre><code>//替换2个变量的值void Swap(int *a,int *b){ int temp = *a; *a =*b; *b = temp;}//抉择排序void SelectionSort(int *arr, int n) { //从数组起始地位开始,i初始化为0; for (int i = 0; i < n - 1; i++) { // 假如以后地位为最小元素 int min_index = i; //遍历待排序区间[i+1 - n-1],如果存在更小的元素则更新min_index for (int j = i + 1; j < n; ++j) { if (arr[min_index] > arr[j]) { //循环查找最小值 min_index = j; } } //查看min_index是否被更新过,是则将最小元素替换到地位i if(min_index!=i) { //应用替换,防止笼罩掉原先地位的元素 Swap(&arr[i], &arr[min_index]); } }}</code></pre><p>Python实现</p><pre><code>def SelectSort(arr): length = len(arr) for i in range(0,length-1): min_tmp=i for j in range(i+1,length): if arr[j]<arr[min_tmp]: min_tmp=j if min_tmp!=i: arr[i],arr[min_tmp] = arr[min_tmp],arr[i] </code></pre></article> ...

February 15, 2024 · 1 min · jiezi

关于排序:美团搜索多业务商品排序探索与实践

随着美团零售商品类业务的一直倒退,美团搜寻在多业务商品排序场景上面临着诸多的挑战。本文介绍了美团搜寻在商品多业务排序上相干的摸索以及实际,心愿能对从事相干工作的同学有所帮忙或者启发。 参考资料[1] 多业务建模在美团搜寻排序中的实际[2] Ma X, Zhao L, Huang G, et al. Entire space multi-task model: An effective approach for estimating post-click conversion rate[C]//The 41st International ACM SIGIR Conference on Research & Development in Information Retrieval. 2018: 1137-1140.[3] Friedman et al., A note on the group lasso and a sparse group lasso.[4] Kendall et al., Multi-Task Learning Using Uncertainty to Weigh Losses for Scene Geometry and Semantics. In CVPR, 2018.[5] Guo et al., Dynamic Task Prioritization for Multitask Learning. In ECCV, 2018.[6] Sheng et al., One Model to Serve All: Star Topology Adaptive Recommender for Multi-Domain CTR Prediction. In CIKM, 2021.[7] Zhou G, Zhu X, Song C, et al. Deep interest network for click-through rate prediction[C]//Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. ACM, 2018: 1059-1068.[8] Zhou G, Mou N, Fan Y, et al. Deep interest evolution network for click-through rate prediction[C]//Proceedings of the AAAI Conference on Artificial Intelligence. 2019, 33: 5941-5948.[9] Feng Y, Lv F, Shen W, et al. Deep Session Interest Network for Click-Through Rate Prediction[J]. arXiv preprint arXiv:1905.06482, 2019.[10] Chen Q, Zhao H, Li W, et al. Behavior sequence transformer for e-commerce recommendation in Alibaba[C]//Proceedings of the 1st International Workshop on Deep Learning Practice for High-Dimensional Sparse Data. 2019: 1-4[11] Kan Ren, Jiarui Qin, Yuchen Fang, Weinan Zhang, Lei Zheng, Weijie Bian, Guorui Zhou, Jian Xu, Yong Yu, Xiaoqiang Zhu, et al. Lifelong sequential modeling with personalized memorization for user response prediction. In SIGIR, 2019.[12] Qi Pi, Weijie Bian, Guorui Zhou, Xiaoqiang Zhu, and Kun Gai. Practice on long sequential user behavior modeling for click-through rate prediction. In KDD, 2019.[13] Jiarui Qin, W. Zhang, Xin Wu, Jiarui Jin, Yuchen Fang, and Y. Yu. User behavior retrieval for click-through rate prediction. In SIGIR, 2020.[14] Search-based User Interest Modeling with Lifelong Sequential Behavior Data for Click-Through Rate Prediction.[15] Transformer 在美团搜寻排序中的实际[16] Ma J, Zhao Z, Yi X, et al. Modeling task relationships in multi-task learning with multi-gate mixture-of-experts[C]//Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2018: 1930-1939.[17] Xi D, Chen Z, Yan P, et al. Modeling the Sequential Dependence among Audience Multi-step Conversions with Multi-task Learning in Targeted Display Advertising[J]. arXiv preprint arXiv:2105.08489, 2021.[18] Burges C J C. From ranknet to lambdarank to lambdamart: An overview[J]. Learning, 2010, 11(23-581): 81.[19] https://en.wikipedia.org/wiki...[20] Liu et al., AutoFIS: Automatic Feature Interaction Selection in Factorization Models for Click-Through Rate Prediction, In ADS-KDD, 2020.[21] Khawar et al., AutoFeature: Searching for Feature Interactions and Their Architectures for Click-through Rate Prediction, In CIKM, 2020.[22] Tang et al., Progressive Layered Extraction (PLE): A Novel Multi-Task Learning (MTL) Model for Personalized Recommendations, In Recsys, 2020.作者简介曹越、瑶鹏、诗晓、李想、家琪、可依、晓江、肖垚、培浩、达遥、陈胜、云森、利前均来自美团平台搜寻与 NLP 部。 ...

November 19, 2021 · 3 min · jiezi

关于排序:数据结构与算法之美1-排序

一、参考数据结构与算法 学习系列目录——更新ing 排序(上):为什么插入排序比冒泡排序更受欢迎? 二、如何剖析一个排序算法?

July 15, 2021 · 1 min · jiezi

关于排序:记录一下看到的一个题

长度为N的数组记录中1-N个数字将数组中的数从小到大排序不能够间接赋值let arr = [1, 6, 3, 2, 9, 10, 11, 4, 7, 5, 12, 8]; let i = 0, temp = 0;while(temp++ < arr.length) { if (arr[i] === i + 1) { i ++ } else { const temp = arr[arr[i] - 1]; arr[arr[i] - 1] = arr[i]; arr[i] = temp; }}

June 29, 2021 · 1 min · jiezi

关于排序:排序算法

/* * 排序算法 */class Solution { public void swap(int[] arr, int i, int j) { int temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } // 间接插入排序, O(n^2), 稳固 public void directInsert(int[] arr) { for (int i = 0; i < arr.length; i ++) { for (int j = 0; j < i; j ++) { if (arr[j] > arr[i]) { int temp = arr[i]; System.arraycopy(arr, j, arr, j + 1, i - j); arr[j] = temp; } } } } // 折半插入排序,O(nlogn),稳固 public void binaryInsert(int[] arr) { for (int i = 0; i < arr.length; i ++) { int left = 0, right = i - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] < arr[i]) { left = mid + 1; } else { right = mid - 1; } } int temp = arr[i]; System.arraycopy(arr, left, arr, left + 1, i - left); arr[left] = temp; } } // 抉择排序, O(n^2),不稳固 public void directSelect(int[] arr) { for (int i = 0; i < arr.length - 1; i ++) { for (int j = i + 1; j < arr.length; j ++) { if (arr[i] > arr[j]) { swap(arr, i, j); } } } } // 堆排序, O(nlogn),不稳固,最大堆 public void heap(int[] arr) { for (int i = arr.length - 1; i >= 0; i --) { for (int j = i / 2 - 1; j >= 0; j --) { if (j * 2 + 1 == i && i % 2 == 1) { if (arr[j] < arr[j * 2 + 1]) { swap(arr, j, j * 2 + 1); } } else { if (arr[j] < arr[j * 2 + 1]) { swap(arr, j, j * 2 + 1); } if (arr[j] < arr[j * 2 + 2]) { swap(arr, j, j * 2 + 2); } } } swap(arr, 0, i); } } // 冒泡排序,O(n^2), 稳固 public void bubble(int[] arr) { for (int i = arr.length - 1; i >= 0; i --) { for (int j = 0; j < i; j ++) { if (arr[j] > arr[j + 1]) { swap(arr, j, j + 1); } } } } // 快排,O(nlogn),不稳固 public void quick(int[] arr) { quickSort(arr, 0, arr.length - 1); } private void quickSort(int[] arr, int left, int right) { if (arr == null || left >= right || arr.length <= 1) { return; } int mid = partition(arr, left, right); quickSort(arr, left, mid); quickSort(arr, mid + 1, right); } private int partition(int[] arr, int left, int right) { int temp = arr[left]; while (left < right) { while (left < right && temp <= arr[right]) { right --; } if (left < right) { arr[left] = arr[right]; left ++; } while (left < right && temp >= arr[left]) { left ++; } if (left < right) { arr[right] = arr[left]; right --; } } arr[left] = temp; return left; } // 归并排序, O(nlogn), 稳固 public void mergeSort(int[] arr) { sort(arr, 0, arr.length - 1); } private void sort(int[] arr, int left, int right) { if (left < right) { int mid = left + (right - left) / 2; sort(arr, left, mid); sort(arr, mid + 1, right); merge(arr, left, mid, right); } } private void merge(int[] arr, int left, int mid, int right) { int[] temp = new int[right - left + 1]; int i = left, j = mid + 1, index = 0; while (i <= mid && j <= right) { if (arr[i] <= arr[j]) { temp[index ++] = arr[i ++]; } else { temp[index ++] = arr[j ++]; } } while (i <= mid) { temp[index ++] = arr[i ++]; } while (j <= right) { temp[index ++] = arr[j ++]; } for (int k = 0; k < temp.length; k ++) { arr[left ++] = temp[k]; } }}

May 22, 2021 · 4 min · jiezi

关于排序:程序员必知的排序算法一-冒泡排序

冒泡排序简介冒泡排序(Bubble Sort),是一种计算机科学畛域的较简略的排序算法。它反复地走访过要排序的元素列,顺次比拟两个相邻的元素,如果程序(如从大到小、首字母从Z到A)谬误就把他们替换过去。走访元素的工作是反复地进行直到没有相邻元素须要替换,也就是说该元素列曾经排序实现。这个算法的名字由来是因为越小的元素会经由替换缓缓“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。 算法原理将邻近的两个数字两两进行比拟,依照从小到大或者从大到小的程序进行替换。 算法原理示意图数组冒泡排序前↓↓↓ 冒泡排序执行过程[gif动图]↓↓↓ 数组冒泡排序后↓↓↓ 代码实现public class Sort { public static void main(String[] args) { int[] arr = {3,44,38,5,47,15,36,26,27,2,46,4,19,50,48}; bubbleSort(arr); //[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50] System.out.println(Arrays.toString(arr)); } /** * 冒泡排序 * @param arr 想要排序的数组 */ public static void bubbleSort(int[] arr){ //管制比拟轮数 for (int i = 0; i < arr.length; i++) { //每轮比拟多少次 for (int j = 0; j < arr.length - i -1; j++) { if (arr[j] > arr[j + 1]) { // temp为一个长期变量,为了存储替换时的长期值 int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }}这里须要思考几个问题外层的循环起到什么作用/外层循环代表什么?外层循环中的 -1 是做什么的?内层的循环起到什么作用/内层循环代表什么?内层循环中的 -1 是做什么的?内层循环中的 -i 是做什么的?长期变量temp起到什么作用?首先须要了解的是冒泡排序是从头开始,两两比拟,将最大的数放到开端。而后再次从头开始,以此类推……。 ...

May 8, 2021 · 1 min · jiezi

关于排序:算法十大经典排序算法动画演示

(PS:原博客戳这里。原博主写的太好了,所以间接转载过去。为了本人可能学习分明,我将代码局部删掉替换成本人写的代码,不便当前查看。) 0、算法概述0.1 算法分类十种常见排序算法能够分为两大类: 比拟类排序:通过比拟来决定元素间的绝对秩序,因为其工夫复杂度不能冲破O(nlogn),因而也称为非线性工夫比拟类排序。非比拟类排序:不通过比拟来决定元素间的绝对秩序,它能够冲破基于比拟排序的工夫下界,以线性工夫运行,因而也称为线性工夫非比拟类排序。  0.2 算法复杂度 0.3 相干概念 稳固:如果a本来在b后面,而a=b,排序之后a依然在b的后面。不稳固:如果a本来在b的后面,而a=b,排序之后 a 可能会呈现在 b 的前面。工夫复杂度:对排序数据的总的操作次数。反映当n变动时,操作次数出现什么法则。空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。 1、冒泡排序(Bubble Sort)冒泡排序是一种简略的排序算法。它反复地走访过要排序的数列,一次比拟两个元素,如果它们的程序谬误就把它们替换过去。走访数列的工作是反复地进行直到没有再须要替换,也就是说该数列曾经排序实现。这个算法的名字由来是因为越小的元素会经由替换缓缓“浮”到数列的顶端。  1.1 算法形容比拟相邻的元素。如果第一个比第二个大,就替换它们两个;对每一对相邻元素作同样的工作,从开始第一对到结尾的最初一对,这样在最初的元素应该会是最大的数;针对所有的元素反复以上的步骤,除了最初一个;反复步骤1~3,直到排序实现。1.2 动图演示 1.3 代码实现 public static void bubbleSort(int[] array) { for (int i = 0; i < array.length; i++) { for (int j = 0; j < array.length - 1 - i; j++) { if (array[j + 1] < array[j]) { int tmp = array[j + 1]; array[j + 1] = array[j]; array[j] = tmp; } } } }2、抉择排序(Selection Sort)抉择排序(Selection-sort)是一种简略直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,寄存到排序序列的起始地位,而后,再从残余未排序元素中持续寻找最小(大)元素,而后放到已排序序列的开端。以此类推,直到所有元素均排序结束。  ...

November 27, 2020 · 5 min · jiezi

关于排序:干货总结程序员必知必会的十大排序算法

首发公众号:bigsai 转载请分割 文章已收录在 Github:bigsai-algorithm绪论身为程序员,十大排序是是所有合格程序员所必备和把握的,并且热门的算法比方快排、归并排序还可能问的比拟粗疏,对算法性能和复杂度的把握有要求。bigsai作为一个负责任的Java和数据结构与算法方向的小博主,在这方面必定不能让读者们有所破绽。跟着本篇走,带你捋一捋常见的十大排序算法,轻轻松松把握! 首先对于排序来说大多数人对排序的概念停留在冒泡排序或者JDK中的Arrays.sort(),手写各种排序对很多人来说都是一种奢望,更别说十大排序算法了,不过还好你遇到了本篇文章! 对于排序的分类,次要不同的维度比方复杂度来分、内外部、比拟非比拟等维度来分类。咱们失常讲的十大排序算法是外部排序,咱们更多将他们分为两大类:基于比拟和非比拟这个维度去分排序品种。 非比拟类的有桶排序、基数排序、计数排序。也有很多人将排序演绎为8大排序,那就是因为基数排序、计数排序是建设在桶排序之上或者是一种非凡的桶排序,然而基数排序和计数排序有它特有的特色,所以在这里就将他们演绎为10种经典排序算法。而比拟类排序也可分为比拟类排序也有更粗疏的分法,有基于替换的、基于插入的、基于抉择的、基于归并的,更粗疏的能够看上面的脑图。 替换类冒泡排序冒泡排序,又称起泡排序,它是一种基于替换的排序典型,也是快排思维的根底,冒泡排序是一种稳固排序算法,工夫复杂度为O(n^2).根本思维是:循环遍历屡次每次从前往后把大元素往后调,每次确定一个最大(最小)元素,屡次后达到排序序列。(或者从后向前把小元素往前调)。 具体思维为(把大元素往后调): 从第一个元素开始往后遍历,每到一个地位判断是否比前面的元素大,如果比前面元素大,那么就替换两者大小,而后持续向后,这样的话进行一轮之后就能够保障最大的那个数被替换替换到最末的地位能够确定。第二次同样从开始起向后判断着后退,如果以后地位比前面一个地位更大的那么就和他前面的那个数替换。然而有点留神的是,这次并不需要判断到最初,只须要判断到倒数第二个地位就行(因为第一次咱们曾经确定最大的在倒数第一,这次的目标是确定倒数第二)同理,前面的遍历长度每次减一,直到第一个元素使得整个元素有序。例如2 5 3 1 4排序过程如下: 实现代码为: public void maopaosort(int[] a) { // TODO Auto-generated method stub for(int i=a.length-1;i>=0;i--) { for(int j=0;j<i;j++) { if(a[j]>a[j+1]) { int team=a[j]; a[j]=a[j+1]; a[j+1]=team; } } }}疾速排序疾速排序是对冒泡排序的一种改良,采纳递归分治的办法进行求解。而快排相比冒泡是一种不稳固排序,工夫复杂度最坏是O(n^2),均匀工夫复杂度为O(nlogn),最好状况的工夫复杂度为O(nlogn)。 对于快排来说,根本思维是这样的 快排须要将序列变成两个局部,就是序列右边全副小于一个数,序列右面全副大于一个数,而后利用递归的思维再将左序列当成一个残缺的序列再进行排序,同样把序列的右侧也当成一个残缺的序列进行排序。其中这个数在这个序列中是能够随机取的,能够取最右边,能够取最左边,当然也能够取随机数。然而通常不优化状况咱们取最右边的那个数。 实现代码为: public void quicksort(int [] a,int left,int right){ int low=left; int high=right; //上面两句的程序肯定不能混,否则会产生数组越界!!!very important!!! if(low>high)//作为判断是否截止条件 return; int k=a[low];//额定空间k,取最左侧的一个作为掂量,最初要求左侧都比它小,右侧都比它大。 while(low<high)//这一轮要求把左侧小于a[low],右侧大于a[low]。 { while(low<high&&a[high]>=k)//右侧找到第一个小于k的进行 { high--; } //这样就找到第一个比它小的了 a[low]=a[high];//放到low地位 while(low<high&&a[low]<=k)//在low往右找到第一个大于k的,放到右侧a[high]地位 { low++; } a[high]=a[low]; } a[low]=k;//赋值而后左右递归分治求之 quicksort(a, left, low-1); quicksort(a, low+1, right); }插入类排序间接插入排序间接插入排序在所有排序算法中的是最简略排序形式之一。和咱们上学时候 从前往后、按高矮程序排序,那么一堆高下无序的人群中,从第一个开始,如果后面有比本人高的,就直接插入到适合的地位。始终到队伍的最初一个实现插入整个队列能力满足有序。 ...

November 26, 2020 · 3 min · jiezi

关于排序:菜鸟必看的排序算法简单通俗及代码实现几张图带你吃透排序算法

一、插入排序插入排序算法是所有排序办法中最简略的一种算法,其次要的实现思维是将数据依照肯定的程序一个一个的插入到有序的表中,最终失去的序列就是曾经排序好的数据。 间接插入排序是插入排序算法中的一种,采纳的办法是:在增加新的记录时,应用程序查找的形式找到其要插入的地位,而后将新记录插入。 很多初学者所说的插入排序,实际上指的就是间接插入排序算法,插入排序算法还包含折半插入排序、2-路插入排序,表插入排序和希尔排序等,后序文章都会一一讲到。例如采纳间接插入排序算法将无序表{3,1,7,5,2,4,9,6}进行升序排序的过程为: 首先思考记录 3 ,因为插入排序刚开始,有序表中没有任何记录,所以 3 能够间接增加到有序表中,则有序表和无序表能够如图 1 所示: 图 1 间接插入排序(1) 向有序表中插入记录 1 时,同有序表中记录 3 进行比拟,1<3,所以插入到记录 3 的左侧,如图 2 所示: 图 2 间接插入排序(2) 向有序表插入记录 7 时,同有序表中记录 3 进行比拟,3<7,所以插入到记录 3 的右侧,如图 3 所示: 图 3 间接插入排序(3) 向有序表中插入记录 5 时,同有序表中记录 7 进行比拟,5<7,同时 5>3,所以插入到 3 和 7 两头,如图 4 所示: 图 4 间接插入排序(4) 向有序表插入记录 2 时,同有序表中记录 7进行比拟,2<7,再同 5,3,1别离进行比拟,最终确定 2 位于 1 和 3 两头,如图 5 所示: 图 5 间接插入排序(5) ...

November 5, 2020 · 2 min · jiezi

关于排序:算法分析图解双轴快排

原创公众号:bigsai前言在排序算法中,快排是占比十分多的一环,然而快排其思维始终被考查钻研,也有很多的优化计划。这里次要解说双轴快排的思维和实现。 首选,双轴快排也是一种快排的优化计划,在JDK的Arrays.sort()中被次要应用。所以,把握快排曾经不可能满足咱们的需要,咱们还要学会双轴快排的原理和实现才行。 回顾单轴快排单轴快排也就是咱们常说的一般疾速排序,对于疾速排序我想大家应该都很相熟:基于递归和分治的,工夫复杂度最坏而O(n2),最好和均匀状况为O(nlogn). 而快排的具体思路也很简略,每次在待排序序列中找一个数(通常最左侧多一点),而后在这个序列中将比他小的放它左侧,比它大的放它右侧。 如果运气肯不好遇到O(n)平方的,那的确就很被啦: 实现起来也很容易,这里间接贴代码啦: private static void quicksort(int [] a,int left,int right){ int low=left; int high=right; //上面两句的程序肯定不能混,否则会产生数组越界!!!very important!!! if(low>high)//作为判断是否截止条件 return; int k=a[low];//额定空间k,取最左侧的一个作为掂量,最初要求左侧都比它小,右侧都比它大。 while(low<high)//这一轮要求把左侧小于a[low],右侧大于a[low]。 { while(low<high&&a[high]>=k)//右侧找到第一个小于k的进行 { high--; } //这样就找到第一个比它小的了 a[low]=a[high];//放到low地位 while(low<high&&a[low]<=k)//在low往右找到第一个大于k的,放到右侧a[high]地位 { low++; } a[high]=a[low]; } a[low]=k;//赋值而后左右递归分治求之 quicksort(a, left, low-1); quicksort(a, low+1, right); }双轴快排剖析咱们明天的主题是双轴快排,双轴和单轴的区别你也能够晓得,多一个轴,后面讲了快排很多时候选最左侧元素以这个元素为轴将数据划分为两个区域,递归分治的去进行排序。但单轴很多时候可能会遇到较差的状况就是以后元素可能是最大的或者最小的,这样子元素就没有被划分区间,快排的递推T(n)=T(n-1)+O(n)从而为O(n2). 双轴就是选取两个主元素现实将区间划为3局部,这样不仅每次可能确定元素个数增多为2个,划分的区间由原来的两个变成三个,最坏最坏的状况就是左右同大小并且都是最大或者最小,但这样的概率相比一个最大或者最小还是低很多很多,所以双轴快排的优化力度还是挺大的。 总体状况剖析至于双轴快排具体是如何工作的呢?其实也不难理解,这里通过一系列图解说双轴快排的执行流程。 首先在初始的状况咱们是选取待排序区间内最左侧、最右侧的两个数值作为pivot1和pivot2 .作为两个轴的存在。同时咱们会提前解决数组最左侧和最右侧的数据会比拟将最小的放在左侧。所以pivot1<pivot2. 而以后这一轮的最终目标是,比privot1小的在privot1左侧,比privot2大的在privot2右侧,在privot1和privot2之间的在两头。 这样进行一次后递归的进行下一次双轴快排,始终到完结,然而在这个执行过程应该去如何解决剖析呢?须要几个参数呢? 假如晓得排序区间[start,end]。数组为arr, pivot1=arr[start],pivot2=arr[end]还须要三个参数left,right和k。 lleft初始为start,[start,left]区域即为小于等于pivot1小的区域(第一个等于)。right与left对应,初始为end,[right,end]为大于等于pivot2的区域(最初一个等于)。k初始为start+1,是一个从左往右遍历的指针,遍历的数值与pivot1,pivot2比拟进行适当替换,当k>=right即可进行。 k替换过程而后你可能会问k遍历时候到底怎么去替换?left和right该如何解决呢?不急我带你缓缓剖析,首先K是在left和right两头的,遍历k的地位和pivot1,pivot2进行比拟: 如果arr[k]<pivot1,那么先++left,而后swap(arr,k,left),因为初始在start在这个过程不完结start先不动。而后k++;持续进行 而如果arr[k]>pivot2.(区间自行安排即可)有点区别的就是right可能间断的大于arr[k],比方9 3 3 9 7如果咱们须要跳过7后面9到3能力失常替换,这和快排的替换思维统一,当然再具体的实现上就是right--到一个适合比arr[k]小的地位。而后swap(arr,k,right)切记此时k不能自加。因为带替换的那个有可能比pivot1还小要和left替换。 ...

November 5, 2020 · 1 min · jiezi

关于排序:值得收藏的十大经典排序算法

一、算法的分类1、概念 将横七竖八的数据元素,通过肯定的办法按关键字顺序排列的过程叫做排序。 2、分类 非线性工夫比拟类排序:通过比拟来决定元素间的绝对秩序,因为其工夫复杂度不能冲破O(nlogn),因而称为非线性工夫比拟类排序。 线性工夫非比拟类排序:不通过比拟来决定元素间的绝对秩序,它能够冲破基于比拟排序的工夫下界,以线性工夫运行,因而称为线性工夫非比拟类排序。 3、比拟 阐明: 稳固:如果a本来在b后面,而a=b,排序之后a依然在b的后面; 不稳固:如果a本来在b的后面,而a=b,排序之后a可能会呈现在b的前面; 内排序:所有排序操作都在内存中实现; 外排序:因为数据太大,因而把数据放在磁盘中,而排序通过磁盘和内存的数据传输能力进行; 二、各算法原理及实现1、冒泡排序(Bubble Sort) ①根本思维:两个数比拟大小,较大的数下沉,较小的数冒起来。 ②算法形容: 比拟相邻的元素。如果第一个比第二个大,就替换它们两个; 对每一对相邻元素作同样的工作,从开始第一对到结尾的最初一对,这样在最初的元素应该会是最大的数; 针对所有的元素反复以上的步骤,除了最初一个; 反复步骤1~3,直到排序实现。 ③动图演示: ④代码实现 public static int[] bubbleSort(int[] array) {if (array.length == 0)return array;for (int i = 0; i < array.length; i++){ for (int j = 0; j < array.length - 1 - i; j++){ if (array[j + 1] < array[j]) { int temp = array[j + 1]; array[j + 1] = array[j]; array[j] = temp; } } } return array;}2、抉择排序(Selection Sort) ...

November 3, 2020 · 5 min · jiezi

关于排序:冒泡排序

冒泡排序思维根本思维: 冒泡排序,相似于水中冒泡,较大的数沉下去,较小的数缓缓冒起来,假如从小到大,即为较大的数缓缓往后排,较小的数缓缓往前排。直观表白,每一趟遍历,将一个最大的数移到序列开端。 算法形容比拟相邻的元素,如果前一个比后一个大,替换之。第一趟排序第1个和第2个一对,比拟与替换,随后第2个和第3个一对比拟替换,这样直到倒数第2个和最初1个,将最大的数挪动到最初一位。第二趟将第二大的数挪动至倒数第二位因而须要n-1趟;代码实现(js)function bubbleSort(nums) { let length = nums.length; for (let i = 0;i < length - 1;i++) { console.log('第', i + 1, '趟'); for (let j = 0;j < length - 1 - i;j++) { if (nums[j] > nums[j + 1]) { let a = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = a; } console.log(nums); } }};bubbleSort([90, 89, 78, 67, 56, 45, 34, 23, 12]); ...

September 21, 2020 · 1 min · jiezi

关于排序:插入排序

public class InsertSort{ public static void main(String[] args) { int[] array = {34,8,64,51,32,21}; int[] a = insertionSort(array); for (int i = 0; i < a.length; i++) { System.out.println(a[i]); } } public static int[] insertionSort(int[] array){ for (int i = 1; i < array.length; i++) { int tmp = array[i]; for (int j = i; j>0 && tmp <array[j-1];j--){ array[j] = array[j-1]; array[j-1] = tmp; } } return array; }}以后元素tmp之前是曾经排好序,那么将以后元素和后面元素一个一个地去比拟,如果但钱元素更小,阐明该元素应该放到后面,也就是说须要替换这两个元素的地位。而后循环执行,直到tmp元素的值比后面的元素要大。这时候,后面的元素就是有序的了。 ...

September 16, 2020 · 1 min · jiezi

关于排序:排序算法之冒泡排序

冒泡排序(Bubble Sort)1.什么是冒泡排序冒泡排序须要反复地走访过要排序的元素列,顺次比拟两个相邻的元素,如果程序(如从大到小、首字母从Z到A)谬误就把他们替换过去。走访元素的工作是反复地进行直到没有相邻元素须要替换,也就是说该元素列曾经排序实现。工夫复杂度为O(n²) 2.算法步骤比拟相邻的元素。如果第一个比第二个大,就替换他们两个。 对每一对相邻元素作同样的工作,从开始第一对到结尾的最初一对。这步做完后,最初的元素会是最大的数。 针对所有的元素反复以上的步骤,除了最初一个。 3.动图演示 4.代码演示 public static void main(String[] args) { //初始化数组 int[] arr = {3,1,2,5,4}; for (int i = 1; i < arr.length; i++) { //设置标记位 boolean flag = true; for (int j = 0; j < arr.length - i; j++) { //如果以后值比下一个值大,则替换单方地位 if(arr[j] > arr[j + 1]){ int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1]= temp; flag =false; } } //如果一次替换都没有,则阐明曾经有序,无需持续循环 if(flag) { break; } } System.out.println(Arrays.toString(arr)); }5.输入后果[1, 2, 3, 4, 5]

August 17, 2020 · 1 min · jiezi

关于排序:前端面试每日-31-第487天

明天的知识点 (2020.08.15) —— 第487天 (我也要出题)[html] html元素哪些标签是不可替换元素?哪些是可替换元素?[css] 应用display: table-cell有什么利用场景呢?[js] 写一个办法对对象中的key进行排序[软技能] 有ios和android两个下载链接,如何把它们合并成一个二维码?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

August 15, 2020 · 1 min · jiezi

关于排序:排序桶排序

前言在数据结构与算法的排序中,咱们很多人可能更多的相熟冒泡排序、疾速排序、归并排序。可能对堆排序、桶排序、计数排数等比拟陌生,其实这个也没啥简单的,算法的排序中,咱们很多人可能更多的相熟冒泡排序、疾速排序、归并排序。可能对堆排序、桶排序、计数排数等比拟陌生,其实这个也没啥简单的,桶排序是所有排序中最简略的排序之一。 没故障老铁,就是最简略的之一。 并且桶排序和计数排序,基数排序有很多类似和渊源之处。前面会进行比照剖析记得先关注! 桶排序思维其实桶排序重要的是它的思维,而不是具体实现,桶排序从字面的意思上看: 桶:若干个桶,阐明此类排序将数据放入若干个桶中。桶:每个桶有容量,桶是有肯定容积的容器,所以每个桶中可能有多个元素。桶:从整体来看,整个排序更心愿桶可能更匀称,即既不溢出(太多)又不太少。 然而这些桶跟排序又有什么关系呢?首先先说下桶排序的思维,百度百科是这么说的 工作的原理是将数组分到无限数量的桶子里。每个桶子再个别排序(有可能再应用别的排序算法或是以递归形式持续应用桶排序进行排序)。桶排序是鸽巢排序的一种演绎后果。当要被排序的数组内的数值是平均调配的时候,桶排序应用线性工夫((n))。但桶排序并不是 比拟排序,他不受到 O(n log n) 上限的影响。用通俗易懂的话来了解: 将待排序的序列分到若干个桶中,每个桶内的元素再进行个别排序。工夫复杂度最好可能是线性O(n),桶排序不是基于比拟的排序当然,桶排序是一种用空间换取工夫的排序。 既然是排序,那么最终的后果必定是从小到大的,桶排序借助桶的地位实现一次初步的排序——将待排序元素别离放至各个桶内。 而咱们通常依据待排序元素整除的办法将其较为平均的放至桶中,如8 5 22 15 28 9 45 42 39 19 27 47 12这个待排序序列,假如放入桶编号的规定为:n/10。这样首先各个元素就能够间接通过整除的办法放至对应桶中。而右侧所有桶内数据都比左侧的要大! 在刚刚放入桶中的时候,各个桶的大小绝对能够确定,右侧都比左侧大,但桶内还是无序的,对各个桶内别离进行排序,再顺次依照桶的程序、桶内序列程序失去一个最终排序的序列。 以上就是桶排序在算法上的思维了。如果应用java具体实现的话思路也很简略:用List[]类型的汇合数组示意桶,每个List代表一个桶,将数据依据整除失去的值间接放到对应编号的汇合外面去,再顺次进行排序就能够了。 桶排序算法剖析下面讲了桶排序的具体思维,然而你是不是总感觉心理没那么虚浮呢,这就完了?总感觉怪怪的是吧? 桶排序的确有很多不一样的中央,无论是算法工夫复杂度还是整个算法的流程,都不如啥快排啦、归并啦这些传统排序来的切实。 工夫复杂度剖析桶排序的工夫复杂度到底是多少? 咱们假如有n个待排序数字。分到m个桶中,如果调配平均这样均匀每个桶有n/m个元素。首先在这里我郑重阐明一下桶排序的算法工夫复杂度有两局部组成: 1.遍历解决每个元素,O(n)级别的一般遍历2.每个桶内再次排序的工夫复杂度总和对于第一个局部,我想大家都应该了解最初排好序的取值遍历一趟的O(n);而第二局部咱们能够进行这样的剖析: 如果桶内元素调配较为平均假如每个桶外部应用的排序算法为疾速排序,那么每个桶内的工夫复杂度为(n/m) log(n/m)。有m个桶,那么工夫复杂度为m * (n/m)log(n/m)=n (log n-log m).所以最终桶排序的工夫复杂度为:O(n)+O(n*(log n- log m))=O(n+n*(log n -log m)) 其中m为桶的个数。咱们有时也会写成O(n+c),其中c=n*(log n -log m); 在这里如果达到极限状况n=m时。就能确保防止桶内排序,将数值放到桶中不须要再排序达到O(n)的排序成果,当然这种状况属于计数排序,前面再详解计数排序记得再回顾。 桶排序实用状况桶排序并且像惯例排序那样没有限度,桶排序有相当的限度。因为桶的个数和大小都是咱们人为设置的。而每个桶又要防止空桶的状况。所以咱们在应用桶排序的时候即须要对待排序数列要求偏平均,又要要求桶的设计兼顾效率和空间。 待排序序列要求平均?我要不平均又会怎么样呢?会这样:这样其实相当于只用了无效的很少个数桶,而再看桶排序的工夫复杂度:O(n+n*(log n -log m))m取向1,log m去向0.整个复杂度变成O(n+nlogn)从级别来看就是O(nlogn),你瞅瞅你瞅瞅,这种状况就跟没用桶一样,就是快排(或其余排序)的工夫复杂度。 那,那不能我搞100000个桶嘛?不能够,真的不能够,如果100000个桶,你看看会造成什么状况:这才短短不到100个数,你为了一一映射用100000个空间,空间内容节约不说,你遍历尽管O(n)也是100000次也比100个的O(nlogn)大上很多啊,真是折了夫人又折兵。 所以当初明确后面说的话了吧:数要绝对均匀分布,桶的个数也要正当设计。总之桶排序是一种用空间换取工夫的排序。在设计桶排序,你须要晓得输出数据的上界和下界,看看数据的散布状况,再思考是否用桶排序,当然如果能用好桶排序,效率还是很高的! 实现一个桶排序在这里用java给大家实现一个桶排序。假如序列为:1 8 7 44 42 46 38 34 33 17 15 16 27 28 24;咱们选用5个桶进行桶排序。 ...

July 29, 2020 · 1 min · jiezi

桶排序就是这么容易

[toc] 前言声明:参考来源互联网,有任何争议可以留言。站在前人的肩上,我们才能看的更远。本教程纯手打,致力于最实用教程,不需要什么奖励,只希望多多转发支持。欢迎来我公众号,希望可以结识你,也可以催更,微信搜索:JavaPub 有任何问题都可以来谈谈 ! 如果看上一篇计数排序,你有没有这样疑问,当每个数据之间跨度过大(如从 0-2亿 数字中排序 20 个数),就需要大量空间消耗。桶排序就是对计数排序的改进。 1.桶排序(Bucket sort)百度百科: 桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是 鸽巢排序 的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间((n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。继续 --> 桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点: 在额外空间充足的情况下,尽量增大桶的数量使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。 桶排序是将待排序集合中处于同一个值域的元素存入同一个桶中,也就是根据元素值特性将集合拆分为多个区域,则拆分后形成的多个桶,从值域上看是处于有序状态的。对每个桶中元素进行排序,则所有桶中元素构成的集合是已排序的。 快速排序是将集合拆分为两个值域,这里称为两个桶,再分别对两个桶进行排序,最终完成排序。桶排序则是将集合拆分为多个桶,对每个桶进行排序,则完成排序过程。两者不同之处在于,快排是在集合本身上进行排序,属于原地排序方式,且对每个桶的排序方式也是快排。桶排序则是提供了额外的操作空间,在额外空间上对桶进行排序,避免了构成桶过程的元素比较和交换操作,同时可以自主选择恰当的排序算法对桶进行排序。2.原理2.1.关键元素值域的划分,也就是元素到桶的映射规则。映射规则需要根据待排序集合的元素分布特性进行选择,若规则设计的过于模糊、宽泛,则可能导致待排序集合中所有元素全部映射到一个桶上,则桶排序向比较性质排序算法演变。若映射规则设计的过于具体、严苛,则可能导致待排序集合中每一个元素值映射到一个桶上,则桶排序向计数排序方式演化。排序算法的选择,从待排序集合中元素映射到各个桶上的过程,并不存在元素的比较和交换操作,在对各个桶中元素进行排序时,可以自主选择合适的排序算法,桶排序算法的复杂度和稳定性,都根据选择的排序算法不同而不同。2.2.算法过程根据待排序集合中最大元素和最小元素的差值范围和映射规则,确定申请的桶个数;遍历待排序集合,将每一个元素移动到对应的桶中;对每一个桶中元素进行排序,并移动到已排序集合中。步骤 3 中提到的已排序集合,和步骤 1、2 中的待排序集合是同一个集合。与计数排序不同,桶排序的步骤 2 完成之后,所有元素都处于桶中,并且对桶中元素排序后,移动元素过程中不再依赖原始集合,所以可以将桶中元素移动回原始集合即可。示意图元素分配到不同桶中: 然后,元素在每个桶中排序: 3.代码基于 Java 的代码,代码逻辑很好理解,使用到插入排序,如果不理解,点击传送。package utils;import java.util.Arrays;/** * @author wangshiyu rodert * @date 2020/6/21 15:13 * @description */public class BucketSort { public static void main(String[] args) throws Exception { int[] array = {2, 1, 5, 3, 4}; BucketSort bucketSort = new BucketSort(); int[] sort = bucketSort.sort(array); System.out.println(Arrays.toString(sort)); } private static final InsertSort insertSort = new InsertSort(); public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); return bucketSort(arr, 5); } private int[] bucketSort(int[] arr, int bucketSize) throws Exception { if (arr.length == 0) { return arr; } int minValue = arr[0]; int maxValue = arr[0]; for (int value : arr) { if (value < minValue) { minValue = value; } else if (value > maxValue) { maxValue = value; } } int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;//向下取整 + 1 int[][] buckets = new int[bucketCount][0]; // 利用映射函数将数据分配到各个桶中 for (int i = 0; i < arr.length; i++) { int index = (int) Math.floor((arr[i] - minValue) / bucketSize); buckets[index] = arrAppend(buckets[index], arr[i]); } int arrIndex = 0; for (int[] bucket : buckets) { if (bucket.length <= 0) { continue; } // 对每个桶进行排序,这里使用了插入排序 bucket = insertSort.sort(bucket); for (int value : bucket) { arr[arrIndex++] = value; } } return arr; } /** * 自动扩容,并保存数据 * * @param arr * @param value */ private int[] arrAppend(int[] arr, int value) { arr = Arrays.copyOf(arr, arr.length + 1); arr[arr.length - 1] = value; return arr; }}class InsertSort { //插入排序 public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的 for (int i = 1; i < arr.length; i++) { // 记录要插入的数据 int tmp = arr[i]; // 从已经排序的序列最右边的开始比较,找到比其小的数 int j = i; while (j > 0 && tmp < arr[j - 1]) { arr[j] = arr[j - 1]; j--; } // 存在比其小的数,插入 if (j != i) { arr[j] = tmp; } } return arr; }}返回结果: ...

June 21, 2020 · 3 min · jiezi

计数排序就是这么容易

[toc] 前言声明:参考来源互联网,有任何争议可以留言。站在前人的肩上,我们才能看的更远。本教程纯手打,致力于最实用教程,不需要什么奖励,只希望多多转发支持。欢迎来我公众号,希望可以结识你,也可以催更,微信搜索:JavaPub 有任何问题都可以来谈谈 ! 计数排序是比较容易的排序算法,但是对数量级较小的整数排序很实用。1.计数排序(Counting Sort)1.1.计数排序(Counting Sort)计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为 (n+k)(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当 O(k)>O(n*log(n)) 的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如 归并排序,堆排序)例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。 计数排序是一个简单的排序算法,看下边原理很容易理解。2.原理2.1.步骤算法的步骤如下:找出待排序的数组中最大和最小的元素统计数组中每个值为i的元素出现的次数,存入数组C的第i项对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1 如果有疑问,看下边一个例子2.2.实例题目题目:数组里有20个随机数,取值范围为从0到10,要求用最快的速度把这20个整数从小到大进行排序。无论是[归并排序](),[冒泡排序]()还是[快速排序]()等等,都是基于元素之间的比较来进行排序的。但是有一种特殊的排序算法叫计数排序,这种排序算法不是基于元素比较,而是利用 数组下标 来确定元素的正确位置。 通过计数排序特性分析题目,我们知道整数的取值范围是从0到10,那么这些整数的值肯定是在0到10这11个数里面。于是我们可以建立一个长度为11的数组,数组下标从0到10,元素初始值全为0,如下所示: 先假设20个随机整数的值是: 9, 3, 5, 4, 9, 1, 2, 7, 8,1,3, 6, 5, 3, 4, 0, 10, 9, 7, 9 让我们先遍历这个无序的随机数组,每一个整数按照其值对号入座,对应数组下标的元素进行 加1 操作。比如第一个整数是 9,那么数组下标为 9 的元素加 1: 第二个整数是3,那么数组下标为 3 的元素加 1: 继续遍历数列并修改数组......最终,数列遍历完毕时,数组的状态如下: 数组中的每一个值,代表了数列中对应整数的出现次数。 有了这个统计结果,排序就很简单了,直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次: 0, 1, 1, 2, 3, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 9, 9, 9, 9, 10 ...

June 20, 2020 · 1 min · jiezi

堆排序就是这么容易

[toc] 原文地址 前言声明:参考来源互联网,有任何争议可以留言。站在前人的肩上,我们才能看的更远。本教程纯手打,致力于最实用教程,不需要什么奖励,只希望多多转发支持。欢迎来我公众号,希望可以结识你,也可以催更,微信搜索:JavaPub 有任何问题都可以来谈谈 ! 堆排序在常用排序算法中属于比较难理解的,本篇就以最简单的方式讲解。如果还有什么疑问,1.什么是堆?弄清楚<font color=#159957>堆排序</font>以前,我们先要知道什么是<font color=#159957>堆</font>?堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。下图: 简单用公式描述一下就是: 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2] 问题二:什么是<font color=#159957>完全二叉树</font>? 百度百科: 一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。2.堆排序百度百科: 堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序是利用<font color=#159957>堆</font>这种数据结构而设计的一种排序算法,堆排序是一种<font color=#159957>选择排序</font>,它的最坏,最好,平均<font color=#159957>时间复杂度</font>均为O(nlogn),它也是<font color=#159957>不稳定排序</font>。 3.原理堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了<font color=#159957>步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。</font> a.假设给定无序序列结构如下 b.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。 c.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。 d.这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。 此时,就将一个无需序列构造成了一个大顶堆。 <font color=#159957>步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。</font> a.将堆顶元素9和末尾元素4进行交。 b.重新调整结构,使其继续满足堆定义。 c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8。 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序。 ...

June 20, 2020 · 2 min · jiezi

python自学日记9选择数据结构

1.编写一个程序从文件中读入一个单词列表,并打印出所有是回文的单词集合这次的回文和以前理解的回文不一样,例子如下: ['deltas','desalt','lasted','salted','slated','staled'] ['retainers','ternaries'] 只要都是相同字母组成的单词都算在回文的单词集合中 按照抓取关键信息转化成自己已经掌握的方法原则,我有两个大致方向,一个是挨个读取每个单词,把单词用前面所说的字母词频计数器分解单词,如果词频计数相同的放到一个集合中;另一个方向是将26个字母通过排列组合方式组成各个单词然后去单词表中比对,显然第二个方法违反了最初的原则,因为这个并没有在我目前的掌握之中,而且特复杂,计算量大。 但是后面发现还有更简单的方法,就是利用已经掌握的排序功能,将每个单词的字母分拆并排序,如果结果相同的放到一个集合里。 def paixu_pinjie(word): t=list(word) t.sort() a=''.join(t) return afin=open('words.txt')d=dict()for line in fin: word=line.strip() b=paixu_pinjie(word) if b not in d: d[b]=word else: d[b].append(word)return dfor key,val in d.items(): if len(val)>1: print(val)---------------------------------------------------------------------------AttributeError Traceback (most recent call last)<ipython-input-14-c76bff3c7e52> in async-def-wrapper() 17 return d 18 ---> 19 for key,val in d.items(): 20 if len(val)>1: 21 print(val)AttributeError: 'str' object has no attribute 'append'这里发现问题在d[b]=word上,word是字符串,所以不能使用append,如果想在后面添加单词编程单词集合,需要将word变成列表: def paixu_pinjie(word): t=list(word) t.sort() a=''.join(t) return afin=open('words.txt')d=dict()for line in fin: word=line.strip() b=paixu_pinjie(word) if b not in d: d[b]=[word] else: d[b].append(word)return dfor key,val in d.items(): if len(val)>1: print(val)这样倒是不报错了,但是发现输出结果里有一个单词的集合,显然最后的for循环后面的代码没生效,发现是因为for上面的return导致的,按说return只能在函数里使用,现在有两个解决方案,一个是把这个return删除,另一个是把上面的写成一个函数。 ...

November 5, 2019 · 1 min · jiezi

快速排序归并排序插入排序

快速排序算法快速排序的思想 代码实现import java.util.Arrays;public class QuickSort { public static void main(String[] args){ QuickSort quickSort = new QuickSort(); int arr[] = {4, 6, 1, 2, 9, 0, 3, 11, 5}; quickSort.quickSort(arr); System.out.println(Arrays.toString(arr)); } public void quickSort(int[] arr){ quickSortSub(arr,0,arr.length - 1); } public void quickSortSub(int[] arr,int low,int high){ if(low < high){ int middle = partition(arr, low, high); quickSortSub(arr, low, middle - 1); quickSortSub(arr,middle + 1,high); } } public int partition(int[] arr,int low,int high){ int base = arr[high]; int i = low - 1; for(int j = low; j <= high - 1; j++){ if(arr[j] <= base){ i++; swap(arr,i,j); } } swap(arr,i+1,high); return i + 1; } public void swap(int[] arr,int i,int j){ int temp = 0; temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }}partition函数的另外一种实现方式实现partition函数有很多种方式,前面介绍的方式是两个指针low和high都是从头开始,向同一个方向移动,high指针在low的前面,high指针标记的是比基准数大的,low指针标记的是比基准数小的 ...

July 14, 2019 · 3 min · jiezi

机器学习在高德搜索建议中的应用优化实践

导读:高德的愿景是:连接真实世界,让出行更美好。为了实现愿景,我们要处理好LBS大数据和用户之间的智能链接。信息检索是其中的关键技术,而搜索建议又是检索服务不可或缺的组成部分。 本文将主要介绍机器学习在高德搜索建议的具体应用,尤其是在模型优化方面进行的一些尝试,这些探索和实践都已历经验证,取得了不错的效果,并且为后来几年个性化、深度学习、向量索引的应用奠定了基础。 对搜索排序模块做重构搜索建议(suggest服务)是指:用户在输入框输入query的过程中,为用户自动补全query或POI(Point of Interest,兴趣点,地理信息系统中可以是商铺、小区、公交站等地理位置标注信息),罗列出补全后的所有候选项,并进行智能排序。 我们希望通过suggest服务:智能提示,降低用户的输入成本。它的特点是:响应快、不承担复杂query的检索,可以把它理解为一个简化版的LBS领域信息检索服务。 和通用IR系统一样,suggest也分为doc(LBS中的doc即为POI)的召回和排序两个阶段。其中,排序阶段主要使用query和doc的文本相关性,以及doc本身的特征(weight、click),进行加权算分排序。 但随着业务的不断发展、特征规模越来越大,人工调参逐渐困难,基于规则的排序方式已经很难得到满意的效果。这种情况下,为了解决业务问题,将不得不打上各种补丁,导致代码难以维护。 因此,我们决定对排序模块进行重构,Learning to Rank无疑是一个好的选择。 面临的挑战:样本构造、模型调优Learning to Rank(LTR)是用机器学习的方法来解决检索系统中的排序问题。业界比较常用的模型是gbrank,loss方案用的最多的是pair wise,这里也保持一致。一般应用LTR解决实际问题,最重要的问题之一就是如何获得样本。 首先,高德地图每天的访问量巨大,这背后隐藏的候选POI更是一个天文数字,想要使用人工标注的方法去获得样本明显不现实。 其次,如果想要使用一些样本自动构造的方法,比如基于用户对POI的点击情况构建样本pair ,也会遇到如下的问题: 容易出现点击过拟合,以前点击什么,以后都给什么结果。有时,用户点击行为也无法衡量真实满意度。suggest前端只展示排序top10结果,更多的结果没有机会展现给用户,自然没有点击。部分用户习惯自己输入完整query进行搜索,而不使用搜索建议的补全结果,统计不到这部分用户的需求。对于这几个问题总结起来就是:无点击数据时,建模很迷茫。但就算有某个POI的点击,却也无法代表用户实际是满意的。 最后,在模型学习中,也面临了特征稀疏性的挑战。统计学习的目标是全局误差的一个最小化。稀疏特征由于影响的样本较少,在全局上影响不大,常常被模型忽略。但是实际中一些中长尾case的解决却往往要依靠这些特征。因此,如何在模型学习过程中进行调优是很重要。 系统建模过程详解上一节,我们描述了建模的两个难题,一个是样本如何构造,另一个是模型学习如何调优。 先看下怎么解决样本构造难题,我们解决方案是: 考量用户在出行场景的行为session,不光看在suggest的某次点击行为,更重要的是,考察用户在出行场景下的行为序列。比如suggest给出搜索建议后,继续搜索的是什么词,出行的地点是去哪里,等等。不是统计某个query下的点击, 而是把session看作一个整体,用户在session最后的点击行为,会泛化到session中的所有query上。详细方案第一步,融合服务端多张日志表,包括搜索建议、搜索、导航等。接着,进行session的切分和清洗。最后,通过把输入session中,末尾query的点击计算到session中所有query上,以此满足实现用户输入session最短的优化目标。 如下图所示: 最终,抽取线上点击日志超过百万条的随机query,每条query召回前N条候选POI。利用上述样本构造方案,最终生成千万级别的有效样本作为gbrank的训练样本。 特征方面,主要考虑了4种建模需求,每种需求都有对应的特征设计方案: 有多个召回链路,包括:不同城市、拼音召回。因此,需要一种特征设计,解决不同召回链路间的可比性。随着用户的不断输入,目标POI不是静态的,而是动态变化的。需要一种特征能够表示不同query下的动态需求。低频长尾query,无点击等后验特征,需要补充先验特征。LBS服务,有很强的区域个性化需求。不同区域用户的需求有很大不同。为实现区域个性化,做到千域千面,首先利用geohash算法对地理空间进行分片,每个分片都得到一串唯一的标识符。从而可以在这个标识符(分片)上分别统计特征。 详细的特征设计,如下表所示: 完成特征设计后,为了更好发挥特征的作用,进行必要的特征工程,包括尺度缩放、特征平滑、去position bias、归一化等。这里不做过多解释。 初版模型,下掉所有规则,在测试集上MRR 有5个点左右的提升,但模型学习也存在一些问题,gbrank特征学习的非常不均匀。树节点分裂时只选择了少数特征,其他特征没有发挥作用。 以上就是前面提到的,建模的第二个难题:模型学习的调优问题。具体来说就是如何解决gbrank特征选择不均匀的问题。接下来,我们详细解释下。 先看下,模型的特征重要度。如下图所示: 经过分析,造成特征学习不均衡的原因主要有: 交叉特征query-click的缺失程度较高,60%的样本该特征值为0。该特征的树节点分裂收益较小,特征无法被选择。然而,事实上,在点击充分的情况下,query-click的点击比city-click更接近用户的真实意图。对于文本相似特征,虽然不会缺失,但是它的正逆序比较低,因此节点分裂收益也比city-click低,同样无法被选择。综上,由于各种原因,导致树模型学习过程中,特征选择时,不停选择同一个特征(city-click)作为树节点,使得其他特征未起到应有的作用。解决这个问题,方案有两种: 方法一:对稀疏特征的样本、低频query的样本进行过采样,从而增大分裂收益。优点是实现简单,但缺点也很明显:改变了样本的真实分布,并且过采样对所有特征生效,无法灵活的实现调整目标。我们选择了方法二来解决。方法二: 调loss function。按两个样本的特征差值,修改负梯度(残差),从而修改该特征的下一轮分裂收益。例如,对于query-click特征非缺失的样本,学习错误时会产生loss,调loss就是给这个loss增加惩罚项loss_diff。随着loss的增加,下一棵树的分裂收益随之增加,这时query-click特征被选作分裂节点的概率就增加了。具体的计算公式如下式: 以上公式是交叉熵损失函数的负梯度,loss_diff 相当于对sigmod函数的一个平移。 差值越大,loss_diff越大,惩罚力度越大,相应的下一轮迭代该特征的分裂收益也就越大。 调loss后,重新训练模型,测试集MRR在初版模型的基础又提升了2个点。同时历史排序case的解决比例从40%提升到70%,效果明显。 写在最后Learning to Rank技术在高德搜索建议应用后,使系统摆脱了策略耦合、依靠补丁的规则排序方式,取得了明显的效果收益。gbrank模型上线后,效果基本覆盖了各频次query的排序需求。 目前,我们已经完成了人群个性化、个体个性化的建模上线,并且正在积极推进深度学习、向量索引、用户行为序列预测在高德搜索建议上的应用。 本文作者:高德技术小哥阅读原文 本文为云栖社区原创内容,未经允许不得转载。

July 10, 2019 · 1 min · jiezi

elementUI系列elementUI中表格的筛选功能和排序功能同时使用

一、前言最近在写项目的时候,发现自己对elementUI的表格,自己想吐槽一下table的组件,可能是没有理解透文档中的说明使用。 二、需求要做成这样的: 然后左侧还有一个类似于导航的切换,也可以是tab切换。左侧是查数据库的,右侧也是查数据库的。 三、分析写代码前分析一下: (1)我打算左侧使用tab切换。原因:这个数据之间有关联,分隔内容。用导航有点大材小用。 (2)右侧的部分打算封装成组件,因为每一个tab的内容和数据很相似。这样也是前端的组件高复用,模块化开发,而且左侧导航是后端控制的,多少条不知道。 四、封装组件,父组件传值给子组件子组件和父组件之间的通信,记住父传子:props属性,子传父:this.$emit()方法,兄弟传兄弟,用vuex。 组件的布局分成三部分:介绍,表格,分页。 1、子组件 父组件给子组件传值,在组件上使用props属性,接受父组件传递的值。 2、父组件那么父组件是怎么传的呢?看下图 父组件的布局 这个是父组件,红框内容就是父组件传值方式,父组件在自己本地需要定义后边的parentData,后边的tableData。前边是传给子组件的接受名。 父组件的data 五、封装组件,子组件传值给父组件为啥子组件还需要给父组件传值,因为子组件的操作改变了数据,对父组件造成了影响。 咱么这个需求中,也是可以选择子组件不给父组件传值的,直接改变子组件中改变父组件传递过来的值。我们就当复习一下父子组件传值。 比如分页这个功能,子组件需要将第几页传给父组件。 子组件的pageChange方法: pageChange(val) { this.middleData.pageNum = val; this.$emit('childData', this.middleData) },那父组件是怎么接受这个页码的呢? childData (val) { this.paramsData.orderBy = val.orderBy; this.paramsData.dir = val.dir; this.paramsData.dimension = val.dimension; this.queryData(); }上述childData方法中父组件方法,参数就是子组件传递的数据。 六、子组件的表格的【筛选功能】和【排序功能】1、我们先看【排序功能】看一下官方文档: 我是需要后端配合使用,远程排序。所以必须设置custom。然后配合sort-change方法。不然就是前端排序 我们来看一下sortChang方法: sortChange (column, event) { this.middleData.orderBy = column.prop; if(column.order =='ascending'){this.middleData.dir = 'asc'}else if(column.order =='descending'){this.middleData.dir = 'desc'}else{this.middleData.dir = ''}; this.$emit('childData', this.middleData) },将查询的参数传递给父组件。 ...

July 5, 2019 · 1 min · jiezi

Feed流系统设计总纲

简介差不多十年前,随着功能机的淘汰和智能机的普及,互联网开始进入移动互联网时代,最具代表性的产品就是微博、微信,以及后来的今日头条、快手等。这些移动化联网时代的新产品在过去几年间借着智能手机的风高速成长。 这些产品都是Feed流类型产品,由于Feed流一般是按照时间“从上往下流动”,非常适合在移动设备端浏览,最终这一类应用就脱颖而出,迅速抢占了上一代产品的市场空间。 Feed流是Feed + 流,Feed的本意是饲料,Feed流的本意就是有人一直在往一个地方投递新鲜的饲料,如果需要饲料,只需要盯着投递点就可以了,这样就能源源不断获取到新鲜的饲料。 在信息学里面,Feed其实是一个信息单元,比如一条朋友圈状态、一条微博、一条咨询或一条短视频等,所以Feed流就是不停更新的信息单元,只要关注某些发布者就能获取到源源不断的新鲜信息,我们的用户也就可以在移动设备上逐条去浏览这些信息单元。 当前最流行的Feed流产品有微博、微信朋友圈、头条的资讯推荐、快手抖音的视频推荐等,还有一些变种,比如私信、通知等,这些系统都是Feed流系统,接下来我们会介绍如何设计一个Feed流系统架构。 Feed流系统特点Feed流本质上是一个数据流,是将 “N个发布者的信息单元” 通过 “关注关系” 传送给 “M个接收者”。 Feed流系统是一个数据流系统,所以我们核心要看数据。从数据层面看,数据分为三类,分别是: 发布者的数据:发布者产生数据,然后数据需要按照发布者组织,需要根据发布者查到所有数据,比如微博的个人页面、朋友圈的个人相册等。关注关系:系统中个体间的关系,微博中是关注,是单向流,朋友圈是好友,是双向流。不管是单向还是双向,当发布者发布一条信息时,该条信息的流动永远是单向的。接收者的数据:从不同发布者那里获取到的数据,然后通过某种顺序(一般为时间)组织在一起,比如微博的首页、朋友圈首页等。这些数据具有时间热度属性,越新的数据越有价值,越新的数据就要排在最前面。针对这三类数据,我们可以有如下定义: 存储库:存储发布者的数据,永久保存。关注表:用户关系表,永久保存。同步库:存储接收者的时间热度数据,只需要保留最近一段时间的数据即可。设计Feed流系统时最核心的是确定清楚产品层面的定义,需要考虑的因素包括: 产品用户规模:用户规模在十万、千万、十亿级时,设计难度和侧重点会不同。关注关系(单向、双写):如果是双向,那么就不会有大V,否则会有大V存在。上述是选择数据存储系统最核心的几个考虑点,除此之外,还有一些需要考虑的:如何实现Meta和Feed内容搜索? 虽然Feed流系统本身可以不需要搜索,但是一个Feed流产品必须要有搜索,否则信息发现难度会加大,用户留存率会大幅下降。Feed流的顺序是时间还是其他分数,比如个人的喜好程度? 双向关系时由于关系很紧密,一定是按时间排序,就算一个关系很紧密的人发了一条空消息或者低价值消息,那我们也会需要关注了解的。单向关系时,那么可能就会存在大V,大V的粉丝数量理论极限就是整个系统的用户数,有一些产品会让所有用户都默认关注产品负责人,这种产品中,该负责人就是最大的大V,粉丝数就是用户规模。接下来,我们看看整个Feed流系统如何设计。Feed流系统设计上一节,我们提前思考了Feed流系统的几个关键点,接下来,在这一节,我们自顶向下来设计一个Feed流系统。 1. 产品定义第一步,我们首先需要定义产品,我们要做的产品是哪一种类型,常见的类型有: 微博类朋友圈类抖音类私信类接着,再详细看一下这几类产品的异同: 类型关注关系是否有大V时效性排序微博类单向有秒~分时间抖音类单向/无有秒~分推荐朋友圈类双向无秒时间私信类双向无秒时间上述对比中,只对比各类产品最核心、或者最根本特点,其他次要的不考虑。比如微博中互相关注后就是双向关注了,但是这个不是微博的立命之本,只是补充,无法撼动根本。 从上面表格可以看出来,主要分为两种区分: 关注关系是单向还是双向: 如果是单向,那么可能就会存在大V效应,同时时效性可以低一些,比如到分钟级别;如果是双向,那就是好友,好友的数量有限,那么就不会有大V,因为每个人的精力有限,他不可能主动加几千万的好友,这时候因为关系更精密,时效性要求会更高,需要都秒级别。排序是时间还是推荐: 用户对feed流最容易接受的就是时间,目前大部分都是时间。但是有一些场景,是从全网数据里面根据用户的喜好给用户推荐和用户喜好度最匹配的内容,这个时候就需要用推荐了,这种情况一般也会省略掉关注了,相对于关注了全网所有用户,比如抖音、头条等。确定了产品类型后,还需要继续确定的是系统设计目标:需要支持的最大用户数是多少?十万、百万、千万还是亿?用户数很少的时候,就比较简单,这里我们主要考虑 亿级用户 的情况,因为如果系统能支持亿级,那么其他量级也能支持。为了支持亿级规模的用户,主要子系统选型时需要考虑水平扩展能力以及一些子系统的可用性和可靠性了,因为系统大了后,任何一个子系统的不稳定都很容易波及整个系统。 2. 存储我们先来看看最重要的存储,不管是哪种同步模式,在存储上都是一样的,我们定义用户消息的存储为存储库。存储库主要满足三个需求: 可靠存储用户发送的消息,不能丢失。否则就找不到自己曾经发布到朋友圈状态了。读取某个人发布过的所有消息,比如个人主页等。数据永久保存。所以,存储库最重要的特征就是两点: 数据可靠、不丢失。由于数据要永久保存,数据会一直增长,所以要易于水平扩展。综上,可以选为存储库的系统大概有两类: 特点分布式NoSQL关系型数据库(分库分表)可靠性极高高水平扩展能力线性需要改造水平扩展速度毫秒无常见系统Tablestore、BigtableMySQL、PostgreSQL对于可靠性,分布式NoSQL的可靠性要高于关系型数据库,这个可能有违很多人的认知。主要是关系型数据库发展很长时间了,且很成熟了,数据放在上面大家放心,而分布式NoSQL数据库发展晚,使用的并不多,不太信任。但是,分布式NoSQL需要存储的数据量更多,对数据可靠性的要求也加严格,所以一般都是存储三份,可靠性会更高。目前在一些云厂商中的关系型数据库因为采用了和分布式NoSQL类似的方式,所以可靠性也得到了大幅提高。水平扩展能力:对于分布式NoSQL数据库,数据天然是分布在多台机器上,当一台机器上的数据量增大后,可以通过自动分裂两部分,然后将其中一半的数据迁移到另一台机器上去,这样就做到了线性扩展。而关系型数据库需要在扩容时再次分库分表。所以,结论是: 如果是自建系统,且不具备分布式NoSQL数据库运维能力,且数据规模不大,那么可以使用MySQL,这样可以撑一段时间。如果是基于云服务,那么就用分布式NoSQL,比如Tablestore或Bigtable。如果数据规模很大,那么也要用分布式NoSQL,否则就是走上一条不归路。如果使用Tablestore,那么存储库表设计结构如下: 主键列第一列主键第二列主键属性列属性列列名user_idmessage_idcontentother解释消息发送者用户ID消息顺序ID,可以使用timestamp。内容其他内容到此,我们确定了存储库的选型,那么系统架构的轮廓有了: 3. 同步系统规模和产品类型,以及存储系统确定后,我们可以确定同步方式,常见的方式有三种: 推模式(也叫写扩散):和名字一样,就是一种推的方式,发送者发送了一个消息后,立即将这个消息推送给接收者,但是接收者此时不一定在线,那么就需要有一个地方存储这个数据,这个存储的地方我们称为:同步库。推模式也叫写扩散的原因是,一个消息需要发送个多个粉丝,那么这条消息就会复制多份,写放大,所以也叫写扩散。这种模式下,对同步库的要求就是写入能力极强和稳定。读取的时候因为消息已经发到接收者的收件箱了,只需要读一次自己的收件箱即可,读请求的量极小,所以对读的QPS需求不大。归纳下,推模式中对同步库的要求只有一个:写入能力强。拉模式(也叫读扩散):这种是一种拉的方式,发送者发送了一条消息后,这条消息不会立即推送给粉丝,而是写入自己的发件箱,当粉丝上线后再去自己关注者的发件箱里面去读取,一条消息的写入只有一次,但是读取最多会和粉丝数一样,读会放大,所以也叫读扩散。拉模式的读写比例刚好和写扩散相反,那么对系统的要求是:读取能力强。另外这里还有一个误区,很多人在最开始设计feed流系统时,首先想到的是拉模式,因为这种和用户的使用体感是一样的,但是在系统设计上这种方式有不少痛点,最大的是每个粉丝需要记录自己上次读到了关注者的哪条消息,如果有1000个关注者,那么这个人需要记录1000个位置信息,这个量和关注量成正比的,远比用户数要大的多,这里要特别注意,虽然在产品前期数据量少的时候这种方式可以应付,但是量大了后就会事倍功半,得不偿失,切记切记。推拉结合模式:推模式在单向关系中,因为存在大V,那么一条消息可能会扩散几百万次,但是这些用户中可能有一半多是僵尸,永远不会上线,那么就存在资源浪费。而拉模式下,在系统架构上会很复杂,同时需要记录的位置信息是天量,不好解决,尤其是用户量多了后会成为第一个故障点。基于此,所以有了推拉结合模式,大部分用户的消息都是写扩散,只有大V是读扩散,这样既控制了资源浪费,又减少了系统设计复杂度。但是整体设计复杂度还是要比推模式复杂。用图表对比: 类型推模式拉模式推拉结合模式写放大高无中读放大无高中用户读取延时毫秒秒秒读写比例1:9999:1~50:50系统要求写能力强读能力强读写都适中常见系统Tablestore、Bigtable等LSM架构的分布式NoSQLRedis、memcache等缓存系统或搜索系统(推荐排序场景)两者结合架构复杂度简单复杂更复杂介绍完同步模式中所有场景和模式后,我们归纳下: 如果产品中是双向关系,那么就采用推模式。如果产品中是单向关系,且用户数少于1000万,那么也采用推模式,足够了。如果产品是单向关系,单用户数大于1000万,那么采用推拉结合模式,这时候可以从推模式演进过来,不需要额外重新推翻重做。永远不要只用拉模式。如果是一个初创企业,先用推模式,快速把系统设计出来,然后让产品去验证、迭代,等客户数大幅上涨到1000万后,再考虑升级为推拉集合模式。如果是按推荐排序,那么是另外的考虑了,架构会完全不一样,这个后面专门文章介绍。如果选择了Tablestore,那么同步库表设计结构如下: 主键列第一列主键第二列主键属性列属性列属性列列名user_idsequence_idsender_idmessage_idother解释消息接收者用户ID消息顺序ID,可以使用timestamp + send_user_id,也可以直接使用Tablestore的自增列。发送者的用户IDstore_table中的message_id列的值,也就是消息ID。通过sender_id和message_id可以到store_table中查询到消息内容其他内容,同步库中不需要包括消息内容。确定了同步库的架构如下: 4. 元数据前面介绍了同步和存储后,整个Feed流系统的基础功能完成了,但是对于一个完整Feed流产品而言,还缺元数据部分,接下来,我们看元数据如何处理: Feed流系统中的元数据主要包括: 用户详情和列表。关注或好友关系。推送session池。我们接下来逐一来看。 4.1 用户详情和列表 主要是用户的详情,包括用户的各种自定义属性和系统附加的属性,这部分的要求只需要根据用户ID查询到就可以了。 可以采用的分布式NoSQL系统或者关系型数据库都可以。 如果使用NoSQL数据库Tablestore,那么用户详情表设计结构如下: 主键顺序第一列主键属性列-1属性列-2......字段名user_idnick_namegenderother备注主键列,用于唯一确定一个用户用户昵称,用户自定义属性用户性别,用户自定义属性其他属性,包括用户自定义属性列和系统附加属性列。Tablestore是FreeSchema类型的,可以随时在任何一行增加新列而不影响原有数据。4.2 关注或好友关系 这部分是存储关系,查询的时候需要支持查询关注列表或者粉丝列表,或者直接好友列表,这里就需要根据多个属性列查询需要索引能力,这里,存储系统也可以采用两类,关系型、分布式NoSQL数据库。 如果已经有了关系型数据库了,且数据量较少,则选择关系型数据库,比如MySQL等。如果数据量比较大,这个时候就有两种选择: 需要分布式事务,可以采用支持分布式事务的系统,比如分布式关系型数据库。使用具有索引的系统,比如云上的Tablestore,更简单,吞吐更高,扩容能力也一并解决了。如果使用Tablestore,那么关注关系表设计结构如下: Table:user_relation_table 主键顺序第一列主键第一列主键属性列属性列Table字段名user_idfollow_user_idtimestampother备注用户ID粉丝用户ID关注时间其他属性列多元索引的索引结构: Table字段名user_idfollow_user_idtimestamp是否Index是是是是否enableSortAndAgg是是是是否store是是是查询的时候: 如果需要查询某个人的粉丝列表:使用TermQuery查询固定user_id,且按照timestamp排序。如果需要查询某个人的关注列表:使用TermQuery查询固定follow_user_id,且按照timestamp排序。当前数据写入Table后,需要5~10秒钟延迟后会在多元索引中查询到,未来会优化到2秒以内。除了使用多元索引外,还可以使用GlobalIndex。4.3 推送session池 ...

July 2, 2019 · 1 min · jiezi

堆排序heapsort

前言堆排序是排序算法中的一种,算法时间复杂度是O(n log(n))。这里主要介绍堆的构建以及怎样通过heapify操作完成堆排序。代码是用C语言完成的,算法不难,大家可以自己用其他语言实现一下。 什么是堆(Heap)Heap需要满足两个条件: 1.Complete Binary Tree :需要是一颗完全二叉树2.Parent > Children:父节点的值一定要大于子节点的值什么是完全二叉树 生成节点的顺序是从上往下、从左往右依次生成 如下图所示: 父节点的值大于子节点的值 如下图所示: 怎么样用代码表示堆1.假设先有这样一颗完全二叉树,它已经是一个堆了 2.1 我们按照从上往下、从左往右的顺序对每个节点数字进行编号,2.2 我们可以用一个一维数组表示 2.3 使用数组的下标来表示一个完全二叉树的好处就是从任何一个节点出发我都可以通过计算来拿到这个节点的父节点和子节点 构建堆1.假设拿到了一堆数字:10 4 3 5 1 2,这些数字放在了一颗完全二叉树上面,如下图所示: 2.heapify: 把完全二叉树调整成堆,我们把这种操作起个名字叫:heapify 1.第一次heapify操作:把4(父节点)、10、3这三个子节点进行比较,找到最大值和父节点进行交换交换后如下图: 2.第二次heapify操作,把4(父节点)、5、1这三个子节点进行比较,找到最大值和父节点进行交换交换后如下图: 3.这样我们就生成了一个堆:满足完全二叉树、父节点值大于子节点的值 123步的代码实现: void swap(int *tree, int max, int i) { int temp = tree[i]; tree[i] = tree[max]; tree[max] = temp; } /** 对一个二叉树进行heapify操作 @param tree 表示二叉树的数组 @param n 二叉树的节点个数 @param i 表示要对哪个节点进行heapify操作 */ void heapify(int *tree, int n, int i) { if (i >= n) { // 递归函数出口 return; } // 找到i节点的两个子节点 int c1 = i*2 + 1; int c2 = i*2 + 2; // 找个三个节点的最大值 假设i是最大值 int max = i; if(c1 < n && tree[c1] > tree[max]) { // c1 < n 表示节点下面没有子节点 max = c1; } if (c2 < n && tree[c2] > tree[max]) { max = c2; } if (max != i) { // max != i b如果i已经是最大值了就不用交换了 swap(tree, max, i); heapify(tree, n, max);//max节点继续heapify } } int main(int argc, const char * argv[]) { @autoreleasepool { int tree[] = {4,10,3,5,1,2}; int n = 6; heapify(tree, n, 0); for (int i = 0; i < n; i++) { printf("%d\n", tree[i]); } } return 0; }输出结果: ...

July 1, 2019 · 2 min · jiezi

八大排序算法使用python实现

这个之前写的,代码已经丢失,待重新写好代码后补上。 一、冒泡排序 冒泡排序算法的运作如下: 比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。以上节选自维基百科 代码实现 二、选择排序 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 以上节选自维基百科 代码实现: def findSmallest(arr): # 用于查找出数组中最小的元素,返回最小元素的索引。 smallest = arr[0] smallest_index = 0 for i in range(1, len(arr)): if smallest > arr[i]: smallest = arr[i] smallest_index = i return smallest_indexdef selectSort(arr): newArr = [] while arr: smallest = findSmallest(arr) newArr.append(arr.pop(smallest)) return newArr三、插入排序 步骤如下 从第一个元素开始,该元素可以认为已经被排序取出下一个元素,在已经排序的元素序列中从后向前扫描如果该元素(已排序)大于新元素,将该元素移到下一位置重复步骤3,直到找到已排序的元素小于或者等于新元素的位置将新元素插入到该位置后重复步骤2~5以上节选自维基百科 代码实现 def insert_sort(data): for k in range(1, len(data)): cur = data[k] j = k while j > 0 and data[j - 1] > cur: data[j] = data[j - 1] j -= 1 data[j] = cur return data四、希尔排序 ...

June 30, 2019 · 1 min · jiezi

数据结构与算法三带你读懂选择排序Selection-sort

1. 基本介绍选择式排序(select sorting)也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。 2. 选择排序思想基本思想是:第一次从 arr[0]~arr[n-1]中选取最小值,与 arr[0]交换,第二次从 arr[1]~arr[n-1]中选取最小值,与 arr[1]交换,第三次从 arr[2]~arr[n-1]中选取最小值,与 arr[2]交换,…,第 i 次从 arr[i-1]~arr[n-1]中选取最小值,与 arr[i-1]交换,…, 第 n-1 次从 arr[n-2]~arr[n-1]中选取最小值,与 arr[n-2]交换,总共通过 n-1 次,得到一个按排序码从小到大排列的有序序列。 3. 选择排序理解![选择排序动图]() 3.1 选择排序图解 1.选择排序一共有数组大小-1 次排序2.每一次排序,又是一个循环,循环规则如下 2.1 先假定当前这个数是最小数 2.2 然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标 2.3 当遍历到数组的最后时,就得到本轮最小数和小标 2.4 交换代码,再继续 4. 选择排序代码/** * @author: Coder编程 * @create: 2019-6-20 22:06 * @description: 选择排序 **/public class SelectionSort { /** * 选择排序 * @param arr 待排序数组 */ public void selectionSort(Integer[] arr) { // 需要遍历获得最小值的次数 // 要注意一点,当要排序 N 个数,已经经过 N-1 次遍历后,已经是有序数列 for (int i = 0; i < arr.length - 1; i++) { int minindex = i; // 用来保存最小值得索引 int min = arr[i]; // 用来保存最小值 for (int j = i + 1; j < arr.length; j++) { if (min > arr[j]) {// 说明假定的最小值,并不是最小 min = arr[j]; // 重置 min minindex = j; // 重置 minIndex } } // 如果假定最小值的索引发生了改变,则进行交换 if(minindex != i){ arr[minindex] = arr[i]; //此时minindex为j,因此i与j交换 arr[i] = min; //最小值给下标i } System.out.format("\n第 %d 趟:\t", i + 1); Arrays.asList(arr).stream().forEach(x -> System.out.print(x + " ")); } } public static void main(String[] args) { //初始数组 Integer arrays[] = {2,9,7,5,3}; // 调用选择排序方法 SelectionSort selection = new SelectionSort(); System.out.print("欢迎个人公众号Coder编程:选择排序前:\t"); Arrays.asList(arrays).stream().forEach(x -> System.out.print(x + " ")); selection.selectionSort(arrays); System.out.print("\n欢迎个人公众号Coder编程:选择排序后:\t"); Arrays.asList(arrays).stream().forEach(x -> System.out.print(x + " ")); } }打印结果 ...

June 26, 2019 · 2 min · jiezi

JavaScript数据结构与算法Sortleetcode原题

排序 时间复杂度(运行次数)我们假设计算机运行一行基础代码需要执行一次运算。 int aFunc(void) { printf("Hello, World!\n"); // 需要执行 1 次 return 0; // 需要执行 1 次}那么上面这个方法需要执行 2 次运算 int aFunc(int n) { for(int i = 0; i<n; i++) { // 需要执行 (n + 1) 次 printf("Hello, World!\n"); // 需要执行 n 次 } return 0; // 需要执行 1 次}这个方法需要 (n + 1 + n + 1) = 2n + 2 次运算。我们把 算法需要执行的运算次数 用 输入大小n 的函数 表示,即 T(n) 。 ...

June 15, 2019 · 5 min · jiezi

当你打开天猫的那一刻推荐系统做了哪些工作

阿里妹导读:当年打开天猫的那一刻,它为你完成了华丽的变身,成为世上独一无二的“天猫”,这就是智能推荐的力量。今天,来自阿里巴巴搜索推荐事业部的算法工程师陈启伟为你介绍天猫如何玩转首页个性化推荐,揭开搜索推荐的神秘面纱。天猫首页作为用户打开手机天猫App的第一印象,所推荐的商品极大地决定了用户接下来的行为,对用户流量的承接与分发、提升用户购物体验和呈现天猫货品的性价比、品质感及品牌力起到至关重要的作用,成为提升天猫用户体验的关键环节之一。 1、场景介绍天猫首页的场景主要包括大促会场入口和日常频道两大类,如图1所示。其中左图为大促会场入口,包括主会场入口和行业会场入口;主会场入口通过为用户推荐7个商品(3个在中间动态轮播)给大促主会场进行引流,引流 UV 达数千万以上;行业会场入口通过为用户推荐4个个性化会场和商品为数万的会场引流。右图为日常频道,包括限时抢购、天猫好物、聚划算、天猫闪降和精选频道;首页通过个性化推荐商品为各个特色的频道引流,通过各个频道来培养用户心智,让用户在天猫逛起来。 过去的首页推荐更多的是在相关性推荐的单一数据目标上进行优化,如今天猫首页的推荐系统不仅仅考虑推荐结果的相关性,还在推荐结果的发现性、多样性等方面上做了更深度的优化,"效率和体验并重"成为天猫首页新的优化目标。Graph Embedding、Transformer、深度学习、知识图谱等新的技术已先后在天猫首页的推荐系统成功落地,为场景带来了两位数的点击率提升和两位数的疲劳度下降。 2、推荐框架天猫首页的个性化推荐系统可以分为召回、排序和机制三个模块。其中,召回模块主要是从全量的商品素材中检索出用户感兴趣的 TopK 个候选商品,排序模块专注于用户对商品的 CTR 预估,机制模块负责后期的流量调控、体验优化、策略调控等和最终的商品排序。整个推荐系统采用 Graph Embedding、Transformer、深度学习、知识图谱、用户体验建模等新的技术构建起来,后面章节将介绍这个推荐系统的关键技术点。 3、召回3.1 Ranki2i Item-CF 是目前应用最广泛的召回算法,其原理是根据两个商品被同时点击的频率来计算两个商品之间的相似度 simScore,得到 i2i 表;然后通过用户的 trigger 去查询 i2i 表,扩展用户感兴趣的商品。Item-CF 的基本算法虽然简单,但是要获得更好的效果,往往需要根据实际的业务场景进行调优。清除爬虫、刷单等噪声数据,合理选择计算商品之间相似度的数据的时间窗口,引入时间衰减,只考虑同一个类目下商品对,归一化、截断、打散等策略对优化 Item-CF 的效果都有很大的帮助。 Ranki2i 是一种改进的 Item-CF 算法,其在 item-CF 得到的两个商品之间的相似度 simScore 的基础上再乘以该 trigger item 所召回的该 target item 在过去一段时间内的 ctr (注意 ctr 的计算需要进行适当的平滑),对 i2i 的 simScore 进行修正,使得 i2i 表不仅考虑了两个商品的点击共现性,还考虑了召回商品的点击率。 我们基于全网的点击数据和天猫首页场景内的日志来计算 Ranki2i 表,并部署在检索系统 Basic Engine 上,对每个访问天猫首页的用户,从基础特征服务系统 ABFS 中获取用户的 trigger,并以此查询 Ranki2i 表来召回用户感兴趣的商品。 经典 Item-CF 类算法直接根据两个商品被同时点击的频率来计算两个商品之间的相似度,在寻找用户点击商品的相似、相关以及搭配商品上都有很大的优势,且其具有简单、性能高等特点,已成为目前应用使用最为广泛的召回算法。然而由于经典 Item-CF 类算法的召回结果的候选集限定在用户的历史行为类目中,并且算法难以结合商品的 Side Information,导致其推荐结果存在发现性弱、对长尾商品的效果差等问题,容易导致推荐系统出现“越推越窄”的问题,从而制约了推荐系统的可持续发展。为了跟精准地给用户推荐心仪的商品,同时维护推荐系统的可持续发展,解决推荐系统的发现性推荐和长尾推荐等问题,我们团队提出了 S3Graph Embeeding 算法和 MIND 算法。 ...

June 3, 2019 · 2 min · jiezi

排序算法一堆排序

前言堆(二叉堆)是一种用于实现优先队列模型的数据结构,堆具有堆序(heap order)性,每个节点的关键字都大于他的父节点的只有根除外(没有父亲),也可以是都小于,子节点与父节点的关系决定了这个堆是最小堆还是最大堆,分别可以用来做升序和降序。使用优先队列理论上可以实现花费O(N log N)时间的排序。 方法对一个数组进行升序的排列,可以先将数组中的N个元素建立一个二叉堆,这个操作花费O(N)时间,然后对该堆执行N此DeleteMin操作。堆中的数据按照从小到大依次离开堆,将这些元素记录到第二个数组中,最后再拷贝回来,以此得到了针对N个元素的排序。每个DeleteMin操作花费了O(log N)的时间,因此总开销是O(N log N)。注意到我们这里使用了一个额外的数组,空间开销加大了(将第二个数组数据拷贝回来只花费O(N),不会显著增加时间消耗)。虽然我们在程序中常常为了节省时间开销而消耗额外的空间,但在这个问题中有个方法可以避免。在每次DeleteMin操作之后,堆缩小了1,,因此堆中最后的单元可以用来存放刚刚删去的元素。 DeleteMin(删除最小元)这是在排序中重点需要的基本堆操作,要找到需要删除的元素很容易,难的是删除,当删除一个最小元时,在根节点产生了一个空穴,为了保证堆的性质,必需将堆中最后一个元素X移动,如果它可以移动到空穴,那么DeleteMin完成,不过这一般不太可能,因此我们将空穴的两个子节点中较小的一个移入空穴,相当于把空穴往下移动了一层,不断重复这个过程直到X可以被放到空穴。这种策略被称作下滤。最小堆与最大堆具有对称的性质,对于父节点大于子节点的最大堆,区别只是每次删除的是最大元,上移的是空穴子节点中较大的,与最小堆相反。 图片演示 代码#define LeftChild(i) (2 * (i) + 1)void PercDown(ElementType A[], int i, int N) { int Child; ElementType Tmp; for (Tmp = A[i]; LeftChild(i) < N; i = Child) { Child = LeftChild(i); if (Child != N - 1 && A[Child + 1] > A[Child]) Child++; if (Tmp < A[Child]) A[i] = A[Child]; else break; } A[i] = Tmp;}void Heapsort(ElementType A[], int N) { int i; for (i = N / 2; i >= 0; i--) PercDown(A, i, N); for (i = N - 1; i > 0; i--) { Swap(&A[0], &A[i]); PercDown(A, 0, i); }}

May 25, 2019 · 1 min · jiezi

用JavaScript实现插入排序

翻译:疯狂的技术宅https://medium.com/@jimrottin...本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章 插入排序的工作原理是选择当前索引 i 处的元素,并从右向左搜索放置项目的正确位置。 实现插入排序插入排序是一种非常简单的算法,最适合大部分已经被排好序的数据。在开始之前,通过可视化演示算法如何运作一个好主意。你可以参考前面的动画来了解插入排序的工作原理。 算法的基本思想是一次选择一个元素,然后搜索并插入到正确的位置。由此才有了这个名字:插入排序。这种操作将会导致数组被分为两个部分 —— 已排序部分和未排序的元素。有些人喜欢把它描绘成两个不同的数组 —— 一个包含所有未排序的元素,而另一个的元素是完全排序的。但是将其描述为一个数组更符合代码的工作方式。 先来看看代码,然后再进行讨论。 const insertionSort = (nums) => { for (let i = 1; i < nums.length; i++) { let j = i - 1 let tmp = nums[i] while (j >= 0 && nums[j] > tmp) { nums[j + 1] = nums[j] j-- } nums[j+1] = tmp } return nums}在插入排序的代码中有两个索引:i 和 j。 i 用来跟踪外循环并表示正在排序的当前元素。它从 1 开始而不是0,因为当我们在新排序的数组中只有一个元素时,是没有什么可做的。所以要从第二个元素开始,并将它与第一个元素进行比较。第二个索引 j 从 i-1 开始,从右往左迭代,一直到找到放置元素的正确位置。在此过程中,我们将每个元素向后移动一个位置,以便为要排序的新元素腾出空间。 ...

May 11, 2019 · 2 min · jiezi

PHP 算法 —— 快速排序

算法原理下列动图来自@五分钟学算法,演示了快速排序算法的原理和步骤。步骤:从数组中选个基准值将数组中大于基准值的放同一边、小于基准值的放另一边,基准值位于中间位置递归的对分列两边的数组再排序代码实现function quickSort($arr){ $len = count($arr); if ($len <= 1) { return $arr; } $v = $arr[0]; $low = $up = array(); for ($i = 1; $i < $len; ++$i) { if ($arr[$i] > $v) { $up[] = $arr[$i]; } else { $low[] = $arr[$i]; } } $low = quickSort($low); $up = quickSort($up); return array_merge($low, array($v), $up);}测试代码:$startTime = microtime(1);$arr = range(1, 10);shuffle($arr);echo “before sort: “, implode(’, ‘, $arr), “\n”;$sortArr = quickSort($arr);echo “after sort: “, implode(’, ‘, $sortArr), “\n”;echo “use time: “, microtime(1) - $startTime, “s\n”;测试结果:before sort: 1, 7, 10, 9, 6, 3, 2, 5, 4, 8after sort: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10use time: 0.0009009838104248s时间复杂度快速排序的时间复杂度在最坏情况下是O(N2),平均的时间复杂度是O(N*lgN)。这句话很好理解:假设被排序的数列中有N个数。遍历一次的时间复杂度是O(N),需要遍历多少次呢?至少lg(N+1)次,最多N次。1) 为什么最少是lg(N+1)次?快速排序是采用的分治法进行遍历的,我们将它看作一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是lg(N+1)。因此,快速排序的遍历次数最少是lg(N+1)次。2) 为什么最多是N次?这个应该非常简单,还是将快速排序看作一棵二叉树,它的深度最大是N。因此,快读排序的遍历次数最多是N次。参考资料快速排序十大经典排序算法动画与解析感谢您的阅读,觉得内容不错,点个赞吧 ????原文地址: https://shockerli.net/post/qu… ...

March 31, 2019 · 1 min · jiezi

前端动画演绎排序算法

文章包含多个可交互案例,可通过博客原文实时查看案例在学习了常用的排序算法之后,打算用动画Demo来生动形象的展现它们。这里包含6种排序算法,其中一半是简单算法,另一半是高级算法:冒泡排序选择排序插入排序~归并排序希尔排序快速排序冒泡排序这可能是最简单的一种,但是速度非常慢。 假设我们按照棒球运动员的身高来排列队列。从最左边开始。比较两个球员如果左边的高一些,就换掉。否则,不做任何操作。向右移动一个位置点击运行案例选择排序也从最左边开始。寻找从当前位置到右边的最矮球员将最矮球员与当前位置的球员交换向右移动一个位置点击运行案例插入排序在大多数情况下,这是基础排序方法中的最佳方法。它的速度是泡泡排序的两倍。 而具体步骤比上面的排序稍微复杂一些。从左边的开始。部分排序左球员选择第一个未排序的球员作为标记球员将比标记球员矮的球员移到右边将标记的球员插入到第一个移动过位置的球员的前一个位置。点击运行案例合并排序合并排序算法的核心是两个已经排序的数组的合并和递归。 如图所示,主要步骤如下:将数字分成两部分合并两部分点击运行案例希尔排序“Shell排序”的名称是以发现它的Donald Shell命名的。它基于插入排序,但是增加了一个新特性,从而极大地提高了插入排序的性能。 主要步骤将数组按区间(例如3)划分为若干组,并对它们进行一直排序,直到所有元素都被划分和排序为止。缩小区间,继续进行分割和排序,直到区间变为1。点击运行案例快速排序在大多数情况下,这是最快的排序。选择一个参考元素(最右边的元素)将数组划分为左子数组(比参考元素小的所有元素)和右子数组(比参考元素大的所有元素)对左子数组和右子数组重复步骤2点击运行案例感谢你花时间阅读这篇文章。如果你喜欢这篇文章,欢迎点赞、收藏和分享,让更多的人看到这篇文章,这也是对我最大的鼓励和支持! 同时欢迎阅读我的更多原创前端技术博客: 苏溪云的博客。

March 28, 2019 · 1 min · jiezi

Java 8中处理集合的优雅姿势——Stream

在Java中,集合和数组是我们经常会用到的数据结构,需要经常对他们做增、删、改、查、聚合、统计、过滤等操作。相比之下,关系型数据库中也同样有这些操作,但是在Java 8之前,集合和数组的处理并不是很便捷。不过,这一问题在Java 8中得到了改善,Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。本文就来介绍下如何使用Stream。特别说明一下,关于Stream的性能及原理不是本文的重点,如果大家感兴趣后面会出文章单独介绍。Stream介绍Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。Stream有以下特性及优点:无存储。Stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。为函数式编程而生。对Stream的任何修改都不会修改背后的数据源,比如对Stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新Stream。惰式执行。Stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。可消费性。Stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。我们举一个例子,来看一下到底Stream可以做什么事情:上面的例子中,获取一些带颜色塑料球作为数据源,首先过滤掉红色的、把它们融化成随机的三角形。再过滤器并删除小的三角形。最后计算出剩余图形的周长。如上图,对于流的处理,主要有三种关键性操作:分别是流的创建、中间操作(intermediate operation)以及最终操作(terminal operation)。Stream的创建在Java 8中,可以有多种方法来创建流。1、通过已有的集合来创建流在Java 8中,除了增加了很多Stream相关的类以外,还对集合类自身做了增强,在其中增加了stream方法,可以将一个集合类转换成流。List<String> strings = Arrays.asList(“Hollis”, “HollisChuang”, “hollis”, “Hello”, “HelloWorld”, “Hollis”);Stream<String> stream = strings.stream();以上,通过一个已有的List创建一个流。除此以外,还有一个parallelStream方法,可以为集合创建一个并行流。这种通过集合创建出一个Stream的方式也是比较常用的一种方式。2、通过Stream创建流可以使用Stream类提供的方法,直接返回一个由指定元素组成的流。Stream<String> stream = Stream.of(“Hollis”, “HollisChuang”, “hollis”, “Hello”, “HelloWorld”, “Hollis”);如以上代码,直接通过of方法,创建并返回一个Stream。Stream中间操作Stream有很多中间操作,多个中间操作可以连接起来形成一个流水线,每一个中间操作就像流水线上的一个工人,每人工人都可以对流进行加工,加工后得到的结果还是一个流。以下是常用的中间操作列表:filterfilter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤掉空字符串:List<String> strings = Arrays.asList(“Hollis”, “”, “HollisChuang”, “H”, “hollis”);strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);//Hollis, , HollisChuang, H, hollismapmap 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);numbers.stream().map( i -> i*i).forEach(System.out::println);//9,4,4,9,49,9,25limit/skiplimit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。以下代码片段使用 limit 方法保理4个元素:List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);numbers.stream().limit(4).forEach(System.out::println);//3,2,2,3sortedsorted 方法用于对流进行排序。以下代码片段使用 sorted 方法进行排序:List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);numbers.stream().sorted().forEach(System.out::println);//2,2,3,3,3,5,7distinctdistinct主要用来去重,以下代码片段使用 distinct 对元素进行去重:List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);numbers.stream().distinct().forEach(System.out::println);//3,2,7,5接下来我们通过一个例子和一张图,来演示下,当一个Stream先后通过filter、map、sort、limit以及distinct处理后会发生什么。代码如下:List<String> strings = Arrays.asList(“Hollis”, “HollisChuang”, “hollis”, “Hello”, “HelloWorld”, “Hollis”);Stream s = strings.stream().filter(string -> string.length()<= 6).map(String::length).sorted().limit(3) .distinct();过程及每一步得到的结果如下图:Stream最终操作Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作(terminal operation)最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能在使用任何中间操作,否则将抛出异常:java.lang.IllegalStateException: stream has already been operated upon or closed俗话说,“你永远不会两次踏入同一条河”也正是这个意思。常用的最终操作如下图:forEachStream 提供了方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:Random random = new Random();random.ints().limit(10).forEach(System.out::println);countcount用来统计流中的元素个数。List<String> strings = Arrays.asList(“Hollis”, “HollisChuang”, “hollis”,“Hollis666”, “Hello”, “HelloWorld”, “Hollis”);System.out.println(strings.stream().count());//7collectcollect就是一个归约操作,可以接受各种做法作为参数,将流中的元素累积成一个汇总结果:List<String> strings = Arrays.asList(“Hollis”, “HollisChuang”, “hollis”,“Hollis666”, “Hello”, “HelloWorld”, “Hollis”);strings = strings.stream().filter(string -> string.startsWith(“Hollis”)).collect(Collectors.toList());System.out.println(strings);//Hollis, HollisChuang, Hollis666, Hollis接下来,我们还是使用一张图,来演示下,前文的例子中,当一个Stream先后通过filter、map、sort、limit以及distinct处理后会,在分别使用不同的最终操作可以得到怎样的结果:下图,展示了文中介绍的所有操作的位置、输入、输出以及使用一个案例展示了其结果。 总结本文介绍了Java 8中的Stream 的用途,优点等。还接受了Stream的几种用法,分别是Stream创建、中间操作和最终操作。Stream的创建有两种方式,分别是通过集合类的stream方法、通过Stream的of方法。Stream的中间操作可以用来处理Stream,中间操作的输入和输出都是Stream,中间操作可以是过滤、转换、排序等。Stream的最终操作可以将Stream转成其他形式,如计算出流中元素的个数、将流装换成集合、以及元素的遍历等。本文作者:hollischuang阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 14, 2019 · 1 min · jiezi

堆排序

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。且完全二叉树可以基于数组存储(父子节点的关系可以用数组下标表示),加持上堆的特性,故可以做堆排序。满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点二叉树;即每一层节点数都达到最大值;即深度为 k 的二叉树节点个数为 2^k-1,则为满二叉树;完全二叉树:若设二叉树的深度为 k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,且第 k 层所有的结点都连续集中在最左边,这就是完全二叉树。堆:基于完全二叉树,分为大顶堆/小顶堆。各节点的数值皆大于其左右子节点的数值(大顶堆),或各节点的数值皆小于其左右子节点的数值(小顶堆)堆的特性大顶堆,节点的数值皆大于子节点的数值,下文都以大顶堆为实例讲解。因为是基于完全二叉树的,所以堆还有一些相应的特性:1、位序为 n 的节点,其父节点位序为 (int) n/22、位序为 n 的节点,其左子节点位序为 2n,右子节点位序为 2n+1(这是按照位序从1开始计算,但对编程语言不太友好,如果位序从0开始计算,则左子节点位序为 2n+1, 右子节点位序为 2n+2)按照数组下标的方式去编号的话如下: 0 / \ 1 2 / \ / \ 3 4 5 6 … / floor(n/2) … / n / \2n+1 2n+2可以发现,所有非叶子节点的序号都会落在 0 ~ (int) N/2-1 区间中,N为节点总数,例如:1、有4个节点,节点编号为03,非叶子节点编号区间值为 01,即编号为 0,1 的节点为非叶子节点。2、有5个节点,节点编号为04,非叶子节点编号区间值为 01,即编号为 0,1 的节点为非叶子节点。3、有6个节点,节点编号为05,非叶子节点编号区间值为 02,即编号为 0,1,2 的节点为非叶子节点。4、有7个节点,节点编号为06,非叶子节点编号区间值为 02,即编号为 0,1,2 的节点为非叶子节点。可以很方便的获取完全二叉树的非叶子节点的序号集合,从 k-1 层开始,依次将各非叶子节点及其左右子节点视为一个完全二叉树,将其调整至大顶堆,直至根节点,这时整个二叉树就是一个大顶堆我们有了非叶子节点的序号及获取左右节点序号的算式,所以很容易就可以将各非叶子节点及其左右子节点组成的完全二叉树调整至大顶堆。同时要注意如果非叶子节点同其左右子节点发生了调整,其左右子节点如果也是非叶子节点的话,也要检测是否破坏了堆特性,如破坏也需进行调整。堆排序堆排序:将初始待排序关键字序列(R0,R1….Rn-1)构建成大顶堆,此堆为初始的无序区将堆顶元素R[0]与最后一个元素R[n-1]交换,此时得到新的无序区(R0,R1,…,Rn-2)和新的有序区(Rn-1),且满足R[1,2…n-2]<=R[n-1]由于交换后新的堆顶R[0]可能违反堆的性质,因此需要对当前无序区(R0,R1,…,Rn-2)调整为新堆,然后再次将R[0]与无序区最后一个元素交换,得到新的无序区(R0,R1….Rn-3)和新的有序区(Rn-2,Rn-1)。不断重复此过程直到有序区的元素个数为n-1(第n-1次调整时完全二叉树只有2个节点,即可有序化),则整个排序过程完成。即堆排序的过程:将待排序数组映射到完全二叉树中,通过调整完全二叉树至大顶堆,获取根节点的值(节点最大值)。那大顶堆的构建如何做呢?我用比较白话的方式讲一下1、从 k 层开始,将每一层子节点的最大值与其父节点的值进行比较调整(如发生交换且子节点为非叶子节点,也要检查子节点的树是否满足了父节点大于子节点的特性)2、重复第一步骤直到根节点,此时根节点的值即为完全二叉树节点中的最大值,即生成了大顶堆源码实例package mainimport ( “fmt”)func main() { // 待排序数组 go 的数组传参是值拷贝 我们用切片引用传值更方便些 var arr = []int{1, 6, 2, 4, 5, 3, 7, 9, 8} HeapSort(arr) fmt.Print(arr)}// 堆排序func HeapSort(arr []int) { arrLength := len(arr) // 每一次的堆构建都能得到一个节点中的最大值(根节点)对于N个待排序数列,N-1次堆构建即可得出有序数列 for i := 0; i < arrLength; i++ { // 无序区长度 arrLengthUnsorted := arrLength - i // 无序区构建为完全二叉树后非叶节点的下标的范围 unLeafNodeIndexRange := int(arrLengthUnsorted/2 - 1) // 从 k - 1 层开始 将非叶节点的子树分治递归构建成大顶堆 for j := unLeafNodeIndexRange; j >= 0; j– { HeapBuild(arr, j, arrLengthUnsorted) } // 打印下标 0 ~ arrLengthUnsorted-1 的数列 fmt.Println(“current heap: “, arr[0:arrLengthUnsorted]) // 一次大顶堆构建完成,根节点为堆最大值,与无序区堆的最后一个节点交换 // 无序区节点数-1 // 破坏了堆结构 开始对新无序区做大顶堆构建 SwapItemOfArray(arr, 0, arrLengthUnsorted-1) }}// 将子树调整为大顶堆func HeapBuild(arr []int, nodeIndex int, arrLengthUnsorted int) { // 完全二叉树子节点下标同父节点下标的关系式 leftChildNodeIndex, rightChildNodeIndex := nodeIndex2+1, nodeIndex2+2 // 防止子节点下标越界 && 子节点数值大于父节点 则交换节点值 if leftChildNodeIndex < arrLengthUnsorted && arr[leftChildNodeIndex] > arr[nodeIndex] { SwapItemOfArray(arr, leftChildNodeIndex, nodeIndex) HeapBuild(arr, leftChildNodeIndex, arrLengthUnsorted) //左子树根节点改变 需调整堆结构 } if rightChildNodeIndex < arrLengthUnsorted && arr[rightChildNodeIndex] > arr[nodeIndex] { SwapItemOfArray(arr, rightChildNodeIndex, nodeIndex) HeapBuild(arr, rightChildNodeIndex, arrLengthUnsorted) //右子树根节点改变 需调整堆结构 }}// 交换数组两个元素func SwapItemOfArray(arr []int, indexX int, indexY int) { temp := arr[indexX] arr[indexX] = arr[indexY] arr[indexY] = temp} ...

February 28, 2019 · 2 min · jiezi

oracle先排序再分页

Oracle排序分页查询和MySQL数据库的语句还不一样,这里做简单的记录。按操作时间排序1SELECT A., ROWNUM RN FROM (SELECT * FROM v_log) A ORDER BY operatetime DESC 结果可以发现,按时间排序了,但是rownum并不是从小到大,因为oracle是先生成rownum,再进行排序,需要在套一层查询按操作时间排序2SELECT T., rownum RN FROM( SELECT * FROM (SELECT * FROM v_log) ORDER BY operatetime DESC ) T结果:顺序正确,rownum正确,在此基础上再套一层查询进行分页按操作时间排序并分页SELECT T2.* from( SELECT T., rownum RN FROM(SELECT * FROM (SELECT * FROM v_log) ORDER BY operatetime DESC )T) T2 WHERE RN BETWEEN 1 and 10测试SELECT * FROM ( SELECT A.“sku”, ROWNUM rn, A.“goods_sn” FROM AMZ_HUOPIN_SKU A WHERE ROWNUM <= 10 ORDER BY A.“goods_sn” DESC) tempWHERE temp.rn > 0;SELECT A.“sku”, A.“goods_sn”, ROWNUM RN FROM AMZ_HUOPIN_SKU A ORDER BY A.“sku” DESC## 子查询先找出所有,然后再rownum,rownum 为伪列,后再排序SELECT A.“sku”, A.“goods_sn”, ROWNUM RN FROM (SELECT * FROM AMZ_HUOPIN_SKU) A ORDER BY A.“sku” DESC## 因为oracle是先生成rownum,再进行排序,需要在套一层查询,即先拍好序,然后再生成rownumSELECT T.“sku”, T.“goods_sn”, ROWNUM RN FROM (SELECT * FROM (SELECT * FROM AMZ_HUOPIN_SKU) ORDER BY “sku” DESC) T## 上边的这两个语句是等价的SELECT T.“sku”, T.“goods_sn”, ROWNUM RN FROM (SELECT * FROM AMZ_HUOPIN_SKU ORDER BY “sku” DESC) TSELECT T2. FROM(SELECT T.“sku”, T.“goods_sn”, ROWNUM RN FROM (SELECT * FROM AMZ_HUOPIN_SKU ORDER BY “sku” DESC) T) T2 WHERE RN BETWEEN 0 AND 10注:本文为转载,原文地址:oracle先排序再分页 ...

February 25, 2019 · 1 min · jiezi

让前端面试不在难(一)

今天开始,我从面试题切入开始做一些详解和记录,争取每个工作日一篇!欢迎关注吐槽!const obj = { a: 1, b: 3, c: -3, f: 5, d: 8 }要求以对象value的大小排序返回[c,a,b,f,d]问题解析: 1、对象是无序的,我们需要转为有序数据结构,其实也就是转为数组然后后再去排序。 2.按value排序简单,但要求是输入key对应的排序,我们需要想办法做对应关系 function sortObj(obj) { //先转为数组 let arr = [] // 遍历json 方法有 Object.keys() for in 用keys以后还得继续遍历key数组,在这我们选用for in for (let item in obj) { // 这一步很关键,我们需要能按照value排序,有需要做key的对应关系,我的做法是这样的 // 把json的每一项push到数组里,并拆分原对象key和value分别对应 arr.push({ key: item, value: obj[item] }) } console.log(arr) } sortObj(obj)打印数组:接下来就简单多了,多于的数组排序方法我就不一一写了,本次只为解决问题function sortObj(obj) { //先转为数组 let arr = [] // 遍历json 方法有 Object.keys() for in 用keys以后还得继续遍历key数组,在这我们选用for in for (let item in obj) { // 这一步很关键,我们需要能按照value排序,有需要做key的对应关系,我的做法是这样的 // 把json的每一项push到数组里,并拆分原对象key和value分别对应 arr.push({ key: item, value: obj[item] }) } arr = arr.sort((a, b) => { return a.value - b.value }) console.log(arr) } sortObj(obj)此时结果为以value有序的数组了接下来遍历数组生成结果function sortObj(obj) { //先转为数组 let arr = [] // 遍历json 方法有 Object.keys() for in 用keys以后还得继续遍历key数组,在这我们选用for in for (let item in obj) { // 这一步很关键,我们需要能按照value排序,有需要做key的对应关系,我的做法是这样的 // 把json的每一项push到数组里,并拆分原对象key和value分别对应 arr.push({ key: item, value: obj[item] }) } arr = arr.sort((a, b) => { return a.value - b.value }) console.log(arr) return arr.map((item) => { return item.key }) } console.log(sortObj(obj))测试ok!在来个es6的方法 let newArr = Object.entries(obj).sort((a, b) => { return a[1] - b[1] }).map((item) => { return item[0] }) console.log(newArr)这个方法看起来很骚,其实原理和最开始的解析类似,Object.entries(obj) 会输入一个数组,数组的每一项是一个数组,内容每一项是原对象每一项的key和value,看下图:解析完毕!您的点赞or吐槽是我持续下去的动力! ...

January 24, 2019 · 1 min · jiezi

MySQL 给数据排序同时追加序号列

无分组select @rownum:=@rownum+1 as autoSeq,sid,pid,oprimary,seqfrom singlechoice,(select @rownum:=0) rorder by pid,sid单字段分组select (@i := case when @tableName=a.tablename then @i + 1 else 1 end ) as rowIndex,a.,(@tableName:=a.tablename)from tablestyle a,(select @i:=0,@tableName:=’’) as tgroup by tablename,fieldnameorder by tablename,(@i := case when @tableName=a.tablename then @i + 1 else 1 end )多字段分组select (@i := case when @tableName=concat(a.dbname,a.tablename) then @i + 1 else 1 end ) as rowIndex,a.,(@tableName:=concat(a.dbname,a.tablename)) as tempfrom tablestyle a,(select @i:=0,@tableName:=’’) as tgroup by dbname,tablename,fieldnameorder by dbname,tablename,(@i := case when @tableName=concat(a.dbname,a.tablename) then @i + 1 else 1 end ) ...

January 16, 2019 · 1 min · jiezi

MongoDB 如何使用内存?为什么内存满了?

最近接到多个MongoDB内存方面的线上case及社区问题咨询,主要集中在:为什么我的 MongoDB 使用了 XX GB 内存?一个机器上部署多个 Mongod 实例/进程,WiredTiger cache 应该如何配置?MongoDB 是否应该使用 SWAP 空间来降低内存压力?MongoDB 内存用在哪?Mongod 进程启动后,除了跟普通进程一样,加载 binary、依赖的各种library 到内存,其作为一个DBMS,还需要负责客户端连接管理,请求处理,数据库元数据、存储引擎等很多工作,这些工作都涉及内存的分配与释放,默认情况下,MongoDB 使用 Google tcmalloc 作为内存分配器,内存占用的大头主要是「存储引擎」与 「客户端连接及请求的处理」。存储引擎 CacheMongoDB 3.2 及以后,默认使用 WiredTiger 存储引擎,可通过 cacheSizeGB 选项配置 WiredTiger 引擎使用内存的上限,一般建议配置在系统可用内存的60%左右(默认配置)。举个例子,如果 cacheSizeGB 配置为 10GB,可以认为 WiredTiger 引擎通过tcmalloc分配的内存总量不会超过10GB。为了控制内存的使用,WiredTiger 在内存使用接近一定阈值就会开始做淘汰,避免内存使用满了阻塞用户请求。目前有4个可配置的参数来支持 wiredtiger 存储引擎的 eviction 策略(一般不需要修改),其含义是:参数默认值含义eviction_target80当 cache used 超过 eviction_target,后台evict线程开始淘汰 CLEAN PAGEeviction_trigger95当 cache used 超过 eviction_trigger,用户线程也开始淘汰 CLEAN PAGEeviction_dirty_target5当 cache dirty 超过 eviction_dirty_target,后台evict线程开始淘汰 DIRTY PAGEeviction_dirty_trigger20当 cache dirty 超过 eviction_dirty_trigger, 用户线程也开始淘汰 DIRTY PAGE在这个规则下,一个正常运行的 MongoDB 实例,cache used 一般会在 0.8 * cacheSizeGB 及以下,偶尔超出问题不大;如果出现 used>=95% 或者 dirty>=20%,并一直持续,说明内存淘汰压力很大,用户的请求线程会阻塞参与page淘汰,请求延时就会增加,这时可以考虑「扩大内存」或者 「换更快的磁盘提升IO能力」。TCP 连接及请求处理MongoDB Driver 会跟 mongod 进程建立 tcp 连接,并在连接上发送数据库请求,接受应答,tcp 协议栈除了为连接维护socket元数据为,每个连接会有一个read buffer及write buffer,用户收发网络包,buffer的大小通过如下sysctl系统参数配置,分别是buffer的最小值、默认值以及最大值,详细解读可以google。net.ipv4.tcp_wmem = 8192 65536 16777216net.ipv4.tcp_rmem = 8192 87380 16777216redhat7(redhat6上并没有导出这么详细的信息) 上通过 ss -m 可以查看每个连接的buffer的信息,如下是一个示例,读写 buffer 分别占了 2357478bytes、2626560bytes,即均在2MB左右;500个类似的连接就会占用掉 1GB 的内存;buffer 占到多大,取决于连接上发送/应答的数据包的大小、网络质量等,如果请求应答包都很小,这个buffer也不会涨到很大;如果包比较大,这个buffer就更容易涨的很大。tcp ESTAB 0 0 127.0.0.1:51601 127.0.0.1:personal-agent skmem:(r0,rb2357478,t0,tb2626560,f0,w0,o0,bl0)除了协议栈上的内存开销,针对每个连接,Mongod 会起一个单独的线程,专门负责处理这条连接上的请求,mongod 为处理连接请求的线程配置了最大1MB的线程栈,通常实际使用在几十KB左右,通过 proc 文件系统看到这些线程栈的实际开销。 除了处理请求的线程,mongod 还有一系列的后台线程,比如主备同步、定期刷新 Journal、TTL、evict 等线程,默认每个线程最大ulimit -s(一般10MB)的线程栈,由于这批线程数量比较固定,占的内存也比较可控。# cat /proc/$pid/smaps7f563a6b2000-7f563b0b2000 rw-p 00000000 00:00 0Size: 10240 kBRss: 12 kBPss: 12 kBShared_Clean: 0 kBShared_Dirty: 0 kBPrivate_Clean: 0 kBPrivate_Dirty: 12 kBReferenced: 12 kBAnonymous: 12 kBAnonHugePages: 0 kBSwap: 0 kBKernelPageSize: 4 kBMMUPageSize: 4 kB线程在处理请求时,需要分配临时buffer存储接受到的数据包,为请求建立上下文(OperationContext),存储中间的处理结果(如排序、aggration等)以及最终的应答结果等。当有大量请求并发时,可能会观察到 mongod 使用内存上涨,等请求降下来后又慢慢释放的行为,这个主要是 tcmalloc 内存管理策略导致的,tcmalloc 为性能考虑,每个线程会有自己的 local free page cache,还有 central free page cache;内存申请时,按 local thread free page cache ==> central free page cache 查找可用内存,找不到可用内存时才会从堆上申请;当释放内存时,也会归还到 cache 里,tcmalloc 后台慢慢再归还给 OS, 默认情况下,tcmalloc 最多会 cache min(1GB,1/8 * system_memory) 的内存, 通过 setParameter.tcmallocMaxTotalThreadCacheBytesParameter 参数可以配置这个值,不过一般不建议修改,尽量在访问层面做调优)tcmalloc cache的管理策略,MongoDB 层暴露了几个参数来调整,一般不需要调整,如果能清楚的理解tcmalloc原理及参数含义,可做针对性的调优;MongoDB tcmalloc 的内存状态可以通过 db.serverStatus().tcmalloc 查看,具体含义可以看 tcmalloc 的文档。重点可以关注下 total_free_bytes ,这个值告诉你有多少内存是 tcmalloc 自己缓存着,没有归还给 OS 的。mymongo:PRIMARY&gt; db.serverStatus().tcmalloc{ “generic” : { “current_allocated_bytes” : NumberLong(“2545084352”), “heap_size” : NumberLong(“2687029248”) }, “tcmalloc” : { “pageheap_free_bytes” : 34529280, “pageheap_unmapped_bytes” : 21135360, “max_total_thread_cache_bytes” : NumberLong(1073741824), “current_total_thread_cache_bytes” : 1057800, “total_free_bytes” : 86280256, “central_cache_free_bytes” : 84363448, “transfer_cache_free_bytes” : 859008, “thread_cache_free_bytes” : 1057800, “aggressive_memory_decommit” : 0, … }}如何控制内存使用?合理配置 WiredTiger cacheSizeGB如果一个机器上只部署 Mongod,mongod 可以使用所有可用内存,则是用默认配置即可。如果机器上多个mongod混部,或者mongod跟其他的一些进程一起部署,则需要根据分给mongod的内存配额来配置 cacheSizeGB,按配额的60%左右配置即可。控制并发连接数TCP连接对 mongod 的内存开销上面已经详细分析了,很多同学对并发有一定误解,认为「并发连接数越高,数据库的QPS就越高」,实际上在大部分数据库的网络模型里,连接数过高都会使得后端内存压力变大、上下文切换开销变大,从而导致性能下降。MongoDB driver 在连接 mongod 时,会维护一个连接池(通常默认100),当有大量的客户端同时访问同一个mongod时,就需要考虑减小每个客户端连接池的大小。mongod 可以通过配置 net.maxIncomingConnections 配置项来限制最大的并发连接数量,防止数据库压力过载。是否应该配置 SWAP官方文档上的建议如下,意思是配置一下swap,避免mongod因为内存使用太多而OOM。For the WiredTiger storage engine, given sufficient memory pressure, WiredTiger may store data in swap space.Assign swap space for your systems. Allocating swap space can avoid issues with memory contention and can prevent the OOM Killer on Linux systems from killing mongod. 开启 SWAP 与否各有优劣,SWAP开启,在内存压力大的时候,会利用SWAP磁盘空间来缓解内存压力,此时整个数据库服务会变慢,但具体变慢到什么程度是不可控的。不开启SWAP,当整体内存超过机器内存上线时就会触发OOM killer把进程干掉,实际上是在告诉你,可能需要扩展一下内存资源或是优化对数据库的访问了。是否开启SWAP,实际上是在「好死」与「赖活着」的选择,个人觉得,对于一些重要的业务场景来说,首先应该为数据库规划足够的内存,当内存不足时,「及时调整扩容」比「不可控的慢」更好。其他尽量减少内存排序的场景,内存排序一般需要更多的临时内存主备节点配置差距不要过大,备节点会维护一个buffer(默认最大256MB)用于存储拉取到oplog,后台从buffer里取oplog不断重放,当备同步慢的时候,这个buffer会持续使用最大内存。控制集合及索引的数量,减少databse管理元数据的内存开销;集合、索引太多,元数据内存开销是一方面的影响,更多的会影响启动加载的效率、以及运行时的性能。本文作者:张友东阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 10, 2019 · 2 min · jiezi

leetcode讲解--937. Reorder Log Files

题目You have an array of logs. Each log is a space delimited string of words.For each log, the first word in each log is an alphanumeric identifier. Then, either:Each word after the identifier will consist only of lowercase letters, or;Each word after the identifier will consist only of digits.We will call these two varieties of logs letter-logs and digit-logs. It is guaranteed that each log has at least one word after its identifier.Reorder the logs so that all of the letter-logs come before any digit-log. The letter-logs are ordered lexicographically ignoring identifier, with the identifier used in case of ties. The digit-logs should be put in their original order.Return the final order of the logs.Example 1:Input: [“a1 9 2 3 1”,“g1 act car”,“zo4 4 7”,“ab1 off key dog”,“a8 act zoo”]Output: [“g1 act car”,“a8 act zoo”,“ab1 off key dog”,“a1 9 2 3 1”,“zo4 4 7”]Note:0 <= logs.length <= 1003 <= logs[i].length <= 100logs[i] is guaranteed to have an identifier, and a word after the identifier.题目地址讲解这道题我看过后立马联想到了另一个题目:953. Verifying an Alien Dictionary。那个题目是判断是否有序,而现在这个题目是要排序。感觉这一题更难。实际做下来确实还是比较难的,主要是字符串的比较,要自己写比较规则。然后在调用系统的sort函数。如果要自己写快排那又要多花不少时间了。有个小窍门,题目要求数字的logs保持原样,而且要放在数组尾部,所以我这里是从后往前遍历数组。Java代码class Solution { public String[] reorderLogFiles(String[] logs) { String[] result = new String[logs.length]; int indexBegin = 0; int indexEnd = logs.length-1; for(int i=logs.length-1;i>=0;i–){ int firstSpaceIndex = logs[i].indexOf(’ ‘); if(logs[i].charAt(firstSpaceIndex+1)>=‘0’ && logs[i].charAt(firstSpaceIndex+1)<=‘9’){ result[indexEnd–] = logs[i]; }else{ result[indexBegin++] = logs[i]; } } // for(int i=0;i<logs.length;i++){ // System.out.println(result[i]); // } StringComparator comparator = new StringComparator(); Arrays.sort(result, 0, indexBegin, comparator); return result; } class StringComparator implements Comparator<String>{ @Override public int compare(String o1, String o2) { int o1IndexOfFirstSpace = o1.indexOf(’ ‘); o1 = o1.substring(o1IndexOfFirstSpace); int o2IndexOfFirstSpace = o2.indexOf(’ ‘); o2 = o2.substring(o2IndexOfFirstSpace); int minLength = o1.length(); boolean o1ShortThano2 = true; if(o1.length()>o2.length()){ minLength = o2.length(); o1ShortThano2 = false; } boolean o1LittleThano2 = true; boolean o1Equalso2 = true; for(int i=0;i<minLength;i++){ if(o1.charAt(i)<o2.charAt(i)) { o1Equalso2 = false; break; }else if(o1.charAt(i)>o2.charAt(i)){ o1LittleThano2 = false; o1Equalso2 = false; break; } } if(o1Equalso2){ if(o1ShortThano2){ return -1; }else{ return 1; } }else{ if(o1LittleThano2){ return -1; }else{ return 1; } } } }} ...

January 4, 2019 · 2 min · jiezi

算法:插入排序

插入排序最近在复习算法导论,总结一下经验蛤插入排序的模式就像是排序一手扑克牌 , 设总共牌库数量为n 当前抽中的牌下标为 i, 有以下论证手中的牌是有序的,并且为[0…i], 手中牌数量为(i)剩余的牌库是无序的,并且为[i+1…n], 剩余牌数量(n - i - 1)整个过程可以概括为:从剩余牌库中依次循环抽取牌,循环n-1次, 抽取中的牌依次比较手中牌的大小,循环次数不定( 因为手中牌是有序的,比较成功就会退出循环 ),最多为i-1次使用Java实现算法则为:public class InsertionSort { public static int[] sort(int[] p) { for (int i = 1; i < p.length; i++) { int currentValue = p[i]; int index = i; while (index > 0 && currentValue < p[index - 1] ) { int a = 0; a = p[index]; p[index] = p[index - 1]; p[index - 1] = a; index–; } } return p; } public static void main(String[] args) { int[] b = InsertionSort.sort(new int[]{5, 2, 4, 6, 1, 3}); for (int i : b) { System.out.println(i); } }}根据循环不变式可以验证以上代码( 使用上面代码的变量 )初始化 : 在i=1时证明循环不变式成立, 手中牌为5,单个元素,排序自然成立.保持: 证明每次循环迭代循环不变式成立, 测试几条数据如下成立手中牌: [5] [2,5]正在抽中的牌: [2] [4]牌库中的牌: [4,6,1,3] [6,1,3]终止: 导致外层循环终止的原因是 i < p.length, i = 1, i ++, 必有i > p.length的一刻,认定排序了整个数组.在内层排序手中牌的循环终止原因是index > 0 && currentValue < p[index - 1] index是当前的手牌下标(下标从1开始), index–;必有index <= 0的一刻,认定排序了整个手中牌数组 ...

December 30, 2018 · 1 min · jiezi

如何选择最合适的排序算法?

1.排序算法分类1.1 比较排序:交换排序:基础冒泡排序、优化冒泡排序、基础快速排序、递归版优化快速排序、循环版优化快速排序插入排序:基础插入排序、希尔优化插入排序选择排序:选择排序、二叉堆排序、多叉堆排序归并排序:归并排序1.2 非比较排序:计数排序桶排序基数排序2.基础概念解读2.1 时间复杂度随着排序数据总量n的变大,排序总耗时的上升情况。有五个符号来表示不同的意思:O(n^2) 大O 表示该排序算法增量情况 <= n^2o(n^2) 小o 表示该排序算法增量情况 < n^2(n^2) 希腊字母theta 表示该排序算法增量情况 == n^2(n^2) 希腊字母小欧米伽 表示该排序算法增量情况 > n^2(n^2) 希腊字母大欧米伽 表示该排序算法增量情况 >= n^22.2 稳定性如果a=b,排序前a在b之前,排序后a还在b之前,则稳定如果a=b,排序前a在b之前,排序后a在b之后,则不稳定2.3 逆序对比如{2, 4, 3, 1}这样一个数列,就有{2, 1},{4, 3},{4, 1},{3, 1}等逆序对,数量为43.排序算法对比4.代码实现(Java)https://github.com/dawnchengx…5.伪代码实现5.1 基础冒泡排序BasicBubbleSort(A) for i=1 to A.length-1 for j=A.length down to i + 1 if A[j] < A[j-1] exchange A[j] with A[j-1]5.2 优化冒泡排序OptimizeBubbleSort(A) for i=1 to A.length-1 didSwap = false; for j=A.length down to i + 1 if A[j] < A[j-1] exchange A[j] with A[j-1] didExchange = true if didExchange == true return5.3 基础快速排序BasicQuickSort(A, p, r) if p < r q = BasicPartition(A, p, r) BasicQuickSort(A, p, q-1) BasicQuickSort(A, q+1, r) BasicPartition(A, p, r) x = A[r] i = p-1 for j=p to r-1 if A[j] <= x i = i + 1 exchange A[i] with A[j] exchange A[i+1] with A[r] return i + 15.4 递归优化快速排序RecuOptimizeQuickSort(A, sort, end) if start < end mid = RecuOptimizePartition(A, start, end) RecuOptimizeQuickSort(A, start, mid-1) RecuOptimizeQuickSort(A, start+1, end)RecuOptimizePartition(A, start, end) 生成介于start和end之间的三个不重复的随机数r1,r2,r3 取A[r1],A[r2],A[r3]这三个数的中位数,并将该中位数的下标赋值给r0 x = A[r0] i = start - 1 for j = start to end - 1 if A[j] <= x i = i + 1 exchange A[i] with A[j] exchange A[i+1] with A[end] return i+15.5 循环优化快速排序LoopOptimizeQuickSort(A) stack = [] start = 0 end = A.length - 1 stack.push(start) stack.push(end) while stack.isNotEmpty() end = stack.pop() start = stack.pop() if start < end base = LoopOptimizePartition(A, start, end) // 右半边 stack.push(base+1) stack.push(end) // 左半边 stack.push(start) stack.push(base-1)LoopOptimizePartition(A, start, end) 生成介于start和end之间的三个不重复的随机数r1,r2,r3 取A[r1],A[r2],A[r3]这三个数的中位数,并将该中位数的下标赋值给r0 x = A[r0] i = start - 1 for j = start to end - 1 if A[j] <= x i = i + 1 exchange A[i] with A[j] exchange A[i+1] with A[end] return i+15.6 基础插入排序InsertionSort(A) for j=2 to A.length key = A[j] i = j - 1 while i > 0 and A[i] > key A[i+1] = A[i] i = i - 1 A[i+1] = key5.7 希尔优化插入排序ShellInsertionSort(A) increment = A.length do { increment = increment/3 + 1 for j = increment to A.length key = A[j] i = j - increment while 0 <= i and A[i] > key A[i+increment] = A[i] i = i - increment A[i+increment] = key }while(1 < increment);5.8 选择排序SelectionSort(A) for i=1 to n-1 min = i for j=i+1 to n if A[min] > A[j] min = j exchange A[min] with A[i]5.9 二叉堆排序HeapSort(A) BuildMaxHeap(A) for i = A.length downto 2 exchange A[1] with A[i] A.heap-size = A.heap-size - 1 MaxHeapify(A, 1)BuildMaxHeap(A) A.heap-size = A.length for i = A.length/2 downto 1 MaxHeapify(A, i)MaxHeapify(A, i) l = LEFT(i) r = RIGHT(i) if l <= a.heap-size and A[l] > A[i] largest = l else largest = i if r <= A.heap-size and A[r] > A[largest] largest = r if largest != i exchange A[i] with A[largest] MaxHeapify(A, largest)LEFT(i) return 2iRIGHT(i) return 2i+15.10 多叉堆排序5.11 归并排序MergeSort(A, p, r) if p < r q = (p+r)/2 (向下取整) MergeSort(A, p, q) MergeSort(A, q+1, r) Merge(A, p, q, r)Merge(A, p, q, r) n1 = q - p + 1 n2 = r - q let L[1..n1+1] and R[1..n2 + 1] be new arrays for i = 1 to n1 L[i] = A[p + i - 1] for j = 1 to n2 R[j] = A[q + j] L[n1+1] = 正无穷大 R[n2+1] = 正无穷大 i = 1 j = 1 for k = p to r if L[i] <= R[j] A[k] = L[i] i = i + 1 else A[k] = R[j] j = j + 15.12 计数排序CountingSort(A, B, k) let C[0…k] be a new array for i = 0 to k C[i] = 0 for j = 1 to A.length C[A[j]] = C[A[j]] + 1 for i = 1 to k C[i] = C[i] + C[i-1] for j = A.length downto 1 B[C[A[j]]] = A[j] C[A[j]] = C[A[j]] - 1 5.13 基数排序https://www.jianshu.com/p/68b…5.14 桶排序https://www.cnblogs.com/zer0Black/p/6169858.htmlBucketSort(A) n = A.length let B[0.. n-1] be a new array for i = 0 to n-1 make B[i] an empty list for i = 1 to n insert A[i] into list B[<nA[i]向下取整>] for i = 0 to n-1 sort list B[i] with insertion sort concatenate the lists B[0],B[1],…,B[n-1] together in order ...

December 27, 2018 · 4 min · jiezi

经典算法:位图排序

最近发现一个有趣的排序算法,通过位图来完成排序。位图排序其实就是基数排序,只不过位图排序的下标是比特位。问题描述输入:一个最多包含n个正整数的文件,每个数都小于n,其中n=10^7。如果在输入文件中有任何正数重复出现就是致命错误。没有其他数据与该正数相关联。输出:按升序排列的输入正数的列表。约束:最多有1MB的内存空间可用,有充足的磁盘存储空间可用。运行时间最多几分钟,运行时间为10秒就不需要进一步优化。一种解决方法是把整个文件分成 40 份,每份 250000 个整数,一个整形占 4 字节,刚好可以在 1MB 的空间里操作。在第一趟遍历中,将大小为 0 至 249999 之间的任何整数都读入内存中,并对这 250000 个整数进行排序,写到输出文件中。第二趟遍历排序 250000 至 499999 之间的整数,依此类推,到第 40 趟结束,我们已经完成了排序。这种排序的代价是要读取输入文件 40 次。而另一种解决方法就是使用位图排序。位图排序一般编程语言的 int 类型所占空间大于等于 4 字节,共 32 位。我们可以用这 32 位来表示 0 到 31 的的数字。假设有一个集合为 {0, 3, 5},在位图里表示就是 0000101001 ,这里省去了前面 22 个 0 。一个 32 位的 int 数可以表示 32 个数字。假设总共有 100 个数,我们只需 (100/32)+1=4 个 int 整数就可以表示这 100 个数,031 储存在第 1 个 int 数,3263 储存在第 2 个 int 数。这样,存储所有数值需要的 int 个数为 10^7 / 32 = 312500, 需要总内存为312500 * 4 / 1024 / 1024 = 1.25M, 1M内存限制跑两趟就可以完成排序。位图排序实现我们可以用 3 个函数来实现位图。函数1:将所有的位都置为0,从而将集合初始化为空。函数2:通过读入文件中的每个整数来建立集合,将每个对应的位置都置为 1。函数3:检验每一位,如果该为为1,就输出对应的整数。位图操作类class BitMap: # maxval 最大值 # bitsperword 一个int数的位数 # shift 能表示 bitsperword 需要的位数, 5 位可以表示 32 这个数 # mask 能表示 bitsperword 需要的位数,用二进制表示 def init(self, maxval, bitsperword=32, shift=5, mask=0b11111): self.bitsperword = bitsperword self.shift = shift self.mask = mask # 初始化位图,相当于函数1 self.x = [0 for i in range(1 + int(maxval / bitsperword))] def set(self, i): # i>>self.shift 操作等同于 i 除于 2^self.shift # i & self.mask 操作等同于 i 对 2^self.shift 求余 # 1 << n 等同于 1 * 2^n self.x[i >> self.shift] |= (1 << (i & self.mask)) # 如果某位上有数,就返回 true def test(self, i): return self.x[i >> self.shift] & (1 << (i & self.mask))设置>>> bit = BitMap(500)>>> bit.x[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]>>> bit.x[2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]# self.x[0] 的二进制为 10>>> bit.set(4)>>> bit.x[18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]# self.x[0] 的二进制为 10010输出位对应的值>>> print (bit.test(1))2排序实现def bitSort(lists, maxval): sortLists = [] bit = BitMap(maxval) for val in lists: bit.set(val) for i in range(maxval): if bit.test(i): sortLists.append(i) return sortLists排序测试>>> lists = [5, 2, 6, 8, 10, 22, 25, 44, 29, 36, 40, 3, 4, 1, 20, 27, 37]>>> print (bitSort(lists, max(lists)))[1, 2, 3, 4, 5, 6, 8, 10, 20, 22, 25, 27, 29, 36, 37, 40]位图操作的优点非常明显,内存占用非常低,非常适合在内存有限时使用。完整代码#!/bin/python# -- coding:utf-8 --class BitMap: # maxval 最大值 # bitsperword 一个int数的位数 # shift 能表示 bitsperword 需要的位数, 5 位可以表示 32 这个数 # mask 能表示 bitsperword 需要的位数,用二进制表示 def init(self, maxval, bitsperword=32, shift=5, mask=0b11111): self.bitsperword = bitsperword self.shift = shift self.mask = mask # 初始化位图,相当于函数1 self.x = [0 for i in range(1 + int(maxval / bitsperword))] def set(self, i): # i>>self.shift 操作等同于 i 除于 2^self.shift # i & self.mask 操作等同于 i 对 2^self.shift 求余 # 1 << n 等同于 1 * 2^n self.x[i >> self.shift] |= (1 << (i & self.mask)) # 如果某位上有数,就返回 true def test(self, i): return self.x[i >> self.shift] & (1 << (i & self.mask))def bitSort(lists, maxval): sortLists = [] bit = BitMap(maxval) for val in lists: bit.set(val) for i in range(maxval): if bit.test(i): sortLists.append(i) return sortListsif name == ‘main’: lists = [5, 2, 6, 8, 10, 22, 25, 44, 29, 36, 40, 3, 4, 1, 20, 27, 37] print (bitSort(lists, max(lists)))参考: 编程珠玑 ...

December 26, 2018 · 3 min · jiezi

一个案例彻底弄懂如何正确使用 mysql inndb 联合索引

有一个业务是查询最新审核的5条数据SELECT id, titleFROM th_contentWHERE audit_time < 1541984478 AND status = ‘ONLINE’ORDER BY audit_time DESC, id DESCLIMIT 5;查看当时的监控情况 cpu 使用率是超过了100%,show processlist看到很多类似的查询都是处于create sort index的状态。查看该表的结构CREATE TABLE th_content ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, title varchar(500) CHARACTER SET utf8 NOT NULL DEFAULT ’’ COMMENT ‘内容标题’, content mediumtext CHARACTER SET utf8 NOT NULL COMMENT ‘正文内容’, audit_time int(11) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘审核时间’, last_edit_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘最近编辑时间’, status enum(‘CREATED’,‘CHECKING’,‘IGNORED’,‘ONLINE’,‘OFFLINE’) CHARACTER SET utf8 NOT NULL DEFAULT ‘CREATED’ COMMENT ‘资讯状态’, PRIMARY KEY (id), KEY idx_at_let (audit_time,last_edit_time)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;索引有一个audit_time在左边的联合索引,没有关于status的索引。分析上面的sql执行的逻辑:从联合索引里找到所有小于该审核时间的主键id(假如在该时间戳之前已经审核了100万条数据,则会在联合索引里取出对应的100万条数据的主键 id)对这100万个 id 进行排序(为的是在下面一步回表操作中优化 I/O 操作,因为很多挨得近的主键可能一次磁盘 I/O 就都取到了)回表,查出100万行记录,然后逐个扫描,筛选出status=‘ONLINE’的行记录最后对查询的结果进行排序(假如有50万行都是ONLINE,则继续对这50万行进行排序)最后因为数据量很大,虽然只取5行,但是按照我们刚刚举的极端例子,实际查询了100万行数据,而且最后还在内存中进行了50万行数据库的内存排序。所以是非常低效的。画了一个示意图,说明第一步的查询过程,粉红色部分表示最后需要回表查询的数据行。图中我按照索引存储规律来YY伪造填充了一些数据,如有不对请留言指出。希望通过这张图大家能够看到联合索引存储的方式和索引查询的方式改进思路 1范围查找向来不太好使用好索引的,如果我们增加一个audit_time, status的联合索引,会有哪些改进呢?ALTER TABLE th_content ADD INDEX idx_audit_status (audit_time, status);mysql> explain select id, title from th_content where audit_time < 1541984478 and status = ‘ONLINE’ order by audit_time desc, id desc limit 5;+—-+————-+————+——-+——————————————+——————+———+——+——–+————-+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+—-+————-+————+——-+——————————————+——————+———+——+——–+————-+| 1 | SIMPLE | th_content | range | idx_at_ft_pt_let,idx_audit_status | idx_audit_status | 4 | NULL | 209754 | Using where |+—-+————-+————+——-+——————————————+——————+———+——+——–+————-+细节:因为audit_time是一个范围查找,所以第二列的索引用不上了,只能用到audit_time,所以key_len是4。而下面思路2中,还是这两个字段key_len则是5。还是分析下在添加了该索引之后的执行过程:从联合索引里找到小于该审核时间的audit_time最大的一行的联合索引然后依次往下找,因为< audit_time是一个范围查找,而第二列索引的值是分散的。所以需要依次往前查找,匹配出满足条件(status=‘ONLINE’)的索引行,直到取到第5行为止。回表查询需要的具体数据在上面的示意图中,粉红色标识满足第一列索引要求的行,依次向前查询,本个叶子节点上筛选到了3条记录,然后需要继续向左,到前一个叶子节点继续查询。直到找到5条满足记录的行,最后回表。改进之处因为在索引里面有status的值,所以在筛选满足status=‘ONLINE’行的时候,就不用回表查询了。在回表的时候只有5行数据的查询了,在iops上会大大减少。该索引的弊端如果idx_audit_status里扫描5行都是status是ONLINE,那么只需扫描5行;如果idx_audit_status里扫描前100万行中,只有4行status是ONLINE,则需要扫描100万零1行,才能得到需要的5行记录。索引需要扫描的行数不确定。改进思路 2ALTER TABLE th_content DROP INDEX idx_audit_status;ALTER TABLE th_content ADD INDEX idx_status_audit (status, audit_time);这样不管是排序还是回表都毫无压力啦。本文作者:周梦康阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 21, 2018 · 1 min · jiezi

搞笑的排序算法-睡觉排序

搞笑的排序算法—睡觉排序import java.util.Arrays;import java.util.concurrent.CountDownLatch;/** * CopyRight (c) 2018 freebug.org Technology Inc. * * @author jimmiesong@yeah.net * @date 2018/11/20 11:17 * @since 1.8 */public class SleepSort { public static void main(String[] args) { // int[] array = new int[]{5446, 584, 1, 4, 3, 777, 8, 9, 33, 6666, 33}; int[] array = getArray(); CountDownLatch latch = new CountDownLatch(array.length); System.out.println(“Array: " + Arrays.toString(array)); for (int i = 0; i < array.length; i++) { final int value = array[i]; new Thread(() -> { latch.countDown(); try { Thread.sleep(value); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print(value + “\n”); }).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } private static int[] getArray() { int loop = (int) (Math.random() * 20); if (loop <= 0) { return getArray(); } int[] array = new int[loop]; for (int i = 0; i < loop; i++) { array[i] = (int) (Math.random() * 10000); } return array; }}以上代码,看得懂的人早已捧腹大笑,看不懂的人,肯定在嗤之以鼻! ...

November 20, 2018 · 1 min · jiezi