作为有意向成为一名程序员的我,到大三下因为做助教才开通博客,惭愧惭愧。好吧,入正题,说说对大一师弟师妹数据结构作业的一些感悟。
数据结构第二章讲的是线性表。对于线性表无非就是顺序表(数组)和链表,数组因为其地址在空间连续,可以进行随机存取,但是也因为连续,在进行增删的时候,需要进行数组元素的移动。其实对于数组来说,经常用来操作的就是对数组元素进行排序,然后对排好序的数组进行某些骚操作(一看到有序数组,脑海就出现了二分查找)。排序方法还是有很多的,冒泡、插入、归并、快排。那就来回顾一下吧。
冒泡:基本思想是把大的元素往下沉,小的元素往上冒,时间复杂度O(n2)。代码贴上
public int[] maoPao(int[] srcArray) { // 只需循环srcArray.length - 1次,不需要第length次,因为比他大的都沉下去了,不用比 for (int i = 0; i < srcArray.length - 1; i++) { // 一轮下来把最大的放到了最后 for (int j = 0; j < srcArray.length - 1; j++) { // 比后一个元素大,往下沉 if (srcArray[j] > srcArray[j+1]) { int tmp = srcArray[j]; srcArray[j] = srcArray[j+1]; srcArray[j+1] = tmp; } } } return srcArray; }
插入:每次取一个元素,插入到之前已排序好的部分数组中,时间复杂度O(n2)代码贴上
public int[] insertSort(int[] srcArray) { for (int i = 1; i < srcArray.length; i++) { int tmp = srcArray[i]; for (int j = i - 1; j >= 0; j--) { // 从部分有序数组从后往前取数据,只要找到第一个小于tmp的,插入进去,就可以跳出这层循环 if (tmp < srcArray[j]) { srcArray[j + 1] = srcArray[j]; srcArray[j] = tmp; }else { srcArray[j + 1] = tmp; break; } } } return srcArray; }
归并:基本思想分治,划分为更小更易求解的子集,然后将结果一步步合并,最终合并成有序数组,时间复杂度为O(nlogn),代码贴上
public void mergeSort(int[] rtArray, int[] srcArray, int left, int right) { // 边界条件当右边大于左边时,进行分治归并 if (left < right) { int mid = (left + right) / 2; mergeSort(rtArray, srcArray, left, mid); mergeSort(rtArray, srcArray, mid + 1, right); merge(rtArray, srcArray, left, mid, right); }else { // 当待排序数组只有一个元素,不用分治,直接copy返回,该判断放在主函数比较好,当待排序数组元素<=1时, // 直接返回,不用进入分治归并函数;放在函数体内每次分治到一个元素时都会进行无用功赋值,为什么说是无用功, // 因为赋值后每次都会回到上一次函数调用,然后进入merge重新赋值 if (left == right) { rtArray[left] = srcArray[left]; } } } public void merge(int[] c, int[] srcArray, int left, int mid, int right) { int indexa = left; int indexb = mid + 1; int indexc = left; while (indexa <= mid && indexb <= right) { if (srcArray[indexa] < srcArray[indexb]) { c[indexc++] = srcArray[indexa++]; }else { c[indexc++] = srcArray[indexb++]; } } while (indexa <= mid) { c[indexc++] = srcArray[indexa++]; } while (indexb <= right) { c[indexc++] = srcArray[indexb++]; } // 把部分有序的元素放回原始数组,进而影响下一轮的归并 for (int i = left; i <= right; i++) { srcArray[i] = c[i]; } }
快排:基本思想是在数组中取一个元素,把小于该元素的其他元素放在该元素左边,大于的放在右边,时间复杂度为O(nlogn),代码
public void quikSort(int[] srcArray, int start, int end) { if (start < end) { // 拿到分割点 int partication = dividePoint(srcArray, start, end); quikSort(srcArray, start, partication); quikSort(srcArray, partication + 1, end); } } public int dividePoint(int[] srcArray, int start, int end) { int key = srcArray[start]; int begin = start; while (begin < end) { // 从后往前比较是因为我们移动元素的时候会把原来的元素覆盖掉,从后往前覆盖掉的是首元素(用于比较的元素), // 因为我们事先已经把它保存在key中,所以不影响 while (begin < end && srcArray[end] >= key) { end--; } srcArray[begin] = srcArray[end]; // 从后往前找到一个小于key的,移到左边 while (begin < end && srcArray[begin] <= key) { begin++; } srcArray[end] = srcArray[begin]; // 从后往前找到一个大于key的,移到右边 } srcArray[begin] = key; // 将key放到begin处,此时的begin为分割点 return begin; }
对于二分查找,难点在于边界的确定,边界问题我也是一直很头疼,一般对边界问题处理我是拿简单的例子代进去尝试来确定是 小于还是小于等于(被自己菜哭)。
下面说说链表:
链表的增删很方便,查询不方便,链表元素插入有头插法和尾插法,然后链表较经常用的操作应该是反转链表(或者逆序遍历链表元素),对于逆向遍历链表,如果使用额外空间,使用栈这一数据结构可以很方便的实现逆序遍历链表;如果不使用额外空间,可以直接把链表反转,改变指针指向。暂时没有想到链表还有哪些操作。。。
借着做助教这个机会重温一遍数据结构,夯实自己的基础,也希望自己可以给师弟师妹更多的帮助。