Day41: 顺序查找与折半查找
顺序查找是一种最简单和最基本的查找方法,它从顺序表的一端开始,依次将每个元素的关键字同给定值 K 进行比较,直到相等或比较完毕还未找到。
二分查找又称折半查找。作为二分查找对象的表必须是顺序存储的有序表,通常假定有序表是按关键字从小到大有序。查找过程是首先取整个有序表 A[0] ~ A[n - 1] 的中点元素 A[mid] (mid = (0 + n -1) / 2) 的关键字同给定值 K 比较,相等则成功,若 K 较小,则对 剩余的左半部分进行同样操作,若 K 较大,则对其剩余的右半部分进行同样的操作。
public class DataArray {
class DataNode {
//键值
int key;
//数据
String content;
DataNode(int paraKey, String paraContent) {
key = paraKey;
content = paraContent;
}
/**
*********************
* Overrides the method claimed in Object, the superclass of any class.
*********************
*/
public String toString() {
return "(" + key + ", " + content + ") ";
}// Of toString
}// Of class DataNode
//数组
DataNode[] data;
//数组长度
int length;
public DataArray(int[] paraKeyArray, String[] paraContentArray) {
length = paraKeyArray.length;
data = new DataNode[length];
for (int i = 0; i < length; i++) {
data[i] = new DataNode(paraKeyArray[i], paraContentArray[i]);
}
}
public String toString() {
String resultString = "I am a data array with " + length + " items.\r\n";
for (int i = 0; i < length; i++) {
resultString += data[i] + " ";
}
return resultString;
}
//顺序查找
public String sequentialSearch(int paraKey) {
data[0].key = paraKey;
int i;
for (i = length - 1; data[i].key != paraKey; i--)
;
return data[i].content;
}
public static void sequentialSearchTest() {
int[] tempUnsortedKeys = { -1, 5, 3, 6, 10, 7, 1, 9 };
String[] tempContents = { "null", "if", "then", "else", "switch", "case", "for", "while" };
DataArray tempDataArray = new DataArray(tempUnsortedKeys, tempContents);
System.out.println(tempDataArray);
System.out.println("Search result of 10 is: " + tempDataArray.sequentialSearch(10));
System.out.println("Search result of 5 is: " + tempDataArray.sequentialSearch(5));
System.out.println("Search result of 4 is: " + tempDataArray.sequentialSearch(4));
}
//二分查找
public String binarySearch(int paraKey) {
int tempLeft = 0;
int tempRight = length - 1;
int tempMiddle = (tempLeft + tempRight) / 2;
while (tempLeft <= tempRight) {
tempMiddle = (tempLeft + tempRight) / 2;
if (data[tempMiddle].key == paraKey) {
return data[tempMiddle].content;
} else if (data[tempMiddle].key <= paraKey) {
tempLeft = tempMiddle + 1;
} else {
tempRight = tempMiddle - 1;
}
}
return "null";
}
public static void binarySearchTest() {
int[] tempSortedKeys = { 1, 3, 5, 6, 7, 9, 10 };
String[] tempContents = { "if", "then", "else", "switch", "case", "for", "while" };
DataArray tempDataArray = new DataArray(tempSortedKeys, tempContents);
System.out.println(tempDataArray);
System.out.println("Search result of 10 is: " + tempDataArray.binarySearch(10));
System.out.println("Search result of 5 is: " + tempDataArray.binarySearch(5));
System.out.println("Search result of 4 is: " + tempDataArray.binarySearch(4));
}
public static void main(String args[]) {
System.out.println("\r\n-------sequentialSearchTest-------");
sequentialSearchTest();
System.out.println("\r\n-------binarySearchTest-------");
binarySearchTest();
}
}
Day42:哈希表
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
1.使用 (最简单的) 除数取余法获得数据存放地址 (下标).
2.使用 (最简单的) 顺移位置法解决冲突.
3.搜索的时间复杂度仅与冲突概率相关, 间接地就与装填因子相关. 如果空间很多, 可以看出时间复杂度为 O(1).
代码:
//哈希表
public DataArray(int[] paraKeyArray, String[] paraContentArray, int paraLength) {
// Step 1. 初始化
length = paraLength;
data = new DataNode[length];
for (int i = 0; i < length; i++) {
data[i] = null;
}
// Step 2. 填充数据
int tempPosition;
for (int i = 0; i < paraKeyArray.length; i++) {
// 哈希表
tempPosition = paraKeyArray[i] % paraLength;
// 找到一个空位置
while (data[tempPosition] != null) {
tempPosition = (tempPosition + 1) % paraLength;
System.out.println("Collision, move forward for key " + paraKeyArray[i]);
}
data[tempPosition] = new DataNode(paraKeyArray[i], paraContentArray[i]);
}
}
//哈希查找
public String hashSearch(int paraKey) {
int tempPosition = paraKey % length;
while (data[tempPosition] != null) {
if (data[tempPosition].key == paraKey) {
return data[tempPosition].content;
} /
System.out.println("Not this one for " + paraKey);
tempPosition = (tempPosition + 1) % length;
}
return "null";
}
//哈希测试
public static void hashSearchTest() {
int[] tempUnsortedKeys = { 16, 33, 38, 69, 57, 95, 86 };
String[] tempContents = { "if", "then", "else", "switch", "case", "for", "while" };
DataArray tempDataArray = new DataArray(tempUnsortedKeys, tempContents, 19);
System.out.println(tempDataArray);
System.out.println("Search result of 95 is: " + tempDataArray.hashSearch(95));
System.out.println("Search result of 38 is: " + tempDataArray.hashSearch(38));
System.out.println("Search result of 57 is: " + tempDataArray.hashSearch(57));
System.out.println("Search result of 4 is: " + tempDataArray.hashSearch(4));
}
public static void main(String args[]) {
System.out.println("\r\n-------sequentialSearchTest-------");
sequentialSearchTest();
System.out.println("\r\n-------binarySearchTest-------");
binarySearchTest();
System.out.println("\r\n-------hashSearchTest-------");
hashSearchTest();
}
Day43:插入排序
插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 。
插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动 。
代码:
//插入排序
public void insertionSort() {
DataNode tempNode;
int j;
for (int i = 2; i < length; i++) {
tempNode = data[i];
//找到插入位置
//移动后面的元素
for (j = i - 1; data[j].key > tempNode.key; j--) {
data[j + 1] = data[j];
}
//插入
data[j + 1] = tempNode;
System.out.println("Round " + (i - 1));
System.out.println(this);
}
}
//测试
public static void insertionSortTest() {
int[] tempUnsortedKeys = { -100, 5, 3, 6, 10, 7, 1, 9 };
String[] tempContents = { "null", "if", "then", "else", "switch", "case", "for", "while" };
DataArray tempDataArray = new DataArray(tempUnsortedKeys, tempContents);
System.out.println(tempDataArray);
tempDataArray.insertionSort();
System.out.println("Result\r\n" + tempDataArray);
}
Day44:希尔排序
希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。该方法又称缩小增量排序。
希尔排序实质上是一种分组插入方法。它的基本思想是:对于n个待排序的数列,取一个小于n的整数gap(gap被称为步长)将待排序元素分成若干个组子序列,所有距离为gap的倍数的记录放在同一个组中;然后,对各组内的元素进行直接插入排序。 这一趟排序完成之后,每一个组的元素都是有序的。然后减小gap的值,并重复执行上述的分组和排序。重复这样的操作,当gap=1时,整个数列就是有序的。
代码:
//希尔排序
public void shellSort() {
DataNode tempNode;
int[] tempJumpArray = { 5, 3, 1 };
int tempJump;
int p;
for (int i = 0; i < tempJumpArray.length; i++) {
tempJump = tempJumpArray[i];
for (int j = 0; j < tempJump; j++) {
for (int k = j + tempJump; k < length; k += tempJump) {
tempNode = data[k];
// 找到插入位置
// 移到后面的位置
for (p = k - tempJump; p >= 0; p -= tempJump) {
if (data[p].key > tempNode.key) {
data[p + tempJump] = data[p];
} else {
break;
}
}
//插入
data[p + tempJump] = tempNode;
}
}
System.out.println("Round " + i);
System.out.println(this);
}
}/
//希尔排序测试
public static void shellSortTest() {
int[] tempUnsortedKeys = { 5, 3, 6, 10, 7, 1, 9, 12, 8, 4 };
String[] tempContents = { "if", "then", "else", "switch", "case", "for", "while", "throw", "until", "do" };
DataArray tempDataArray = new DataArray(tempUnsortedKeys, tempContents);
System.out.println(tempDataArray);
tempDataArray.shellSort();
System.out.println("Result\r\n" + tempDataArray);
}
Day45:冒泡排序
冒泡排序:依次比较相邻的数据,将小数据放在前,大数据放在后;即第一趟先比较第1个和第2个数,大数在后,小数在前,再比较第2个数与第3个数,大数在后,小数在前,以此类推则将最大的数"滚动"到最后一个位置;第二趟则将次大的数滚动到倒数第二个位置......第n-1(n为无序数据的个数)趟即能完成排序。
如果某一趟没有交换, 就表示数据已经有序 (早熟, premature), 可以提前结束了。
代码:
//冒泡排序
public void bubbleSort() {
boolean tempSwapped;
DataNode tempNode;
for (int i = length - 1; i > 1; i--) {
tempSwapped = false;
for (int j = 0; j < i; j++) {
if (data[j].key > data[j + 1].key) {
// 交换
tempNode = data[j + 1];
data[j + 1] = data[j];
data[j] = tempNode;
tempSwapped = true;
}
}
// 如果未发生交换说明数据已经有序
if (!tempSwapped) {
System.out.println("Premature");
break;
}
System.out.println("Round " + (length - i));
System.out.println(this);
}
}
//测试
public static void bubbleSortTest() {
int[] tempUnsortedKeys = { 1, 3, 6, 10, 7, 5, 9 };
String[] tempContents = { "if", "then", "else", "switch", "case", "for", "while" };
DataArray tempDataArray = new DataArray(tempUnsortedKeys, tempContents);
System.out.println(tempDataArray);
tempDataArray.bubbleSort();
System.out.println("Result\r\n" + tempDataArray);
}
Day46:快速排序
快速排序(Quick Sort)使用分治法策略。
它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
快速排序流程:
(1) 从数列中挑出一个基准值。
(2) 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置。
(3) 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序
代码:
public void quickSortRecursive(int paraStart, int paraEnd) {
//结束快速排序的条件
if (paraStart >= paraEnd) {
return;
}
int tempPivot = data[paraEnd].key;
DataNode tempNodeForSwap;
int tempLeft = paraStart;
int tempRight = paraEnd - 1;
// 找到枢纽的位置
// 将小的元素向左移动;大的元素向右移动
while (true) {
while ((data[tempLeft].key < tempPivot) && (tempLeft < tempRight)) {
tempLeft++;
}
while ((data[tempRight].key > tempPivot) && (tempLeft < tempRight)) {
tempRight--;
}
if (tempLeft < tempRight) {
// 交换
System.out.println("Swapping " + tempLeft + " and " + tempRight);
tempNodeForSwap = data[tempLeft];
data[tempLeft] = data[tempRight];
data[tempRight] = tempNodeForSwap;
} else {
break;
}
}
//交换
if (data[tempLeft].key > tempPivot) {
tempNodeForSwap = data[paraEnd];
data[paraEnd] = data[tempLeft];
data[tempLeft] = tempNodeForSwap;
} else {
tempLeft++;
}
System.out.print("From " + paraStart + " to " + paraEnd + ": ");
System.out.println(this);
quickSortRecursive(paraStart, tempLeft - 1);
quickSortRecursive(tempLeft + 1, paraEnd);
}
//快速排序
public void quickSort() {
quickSortRecursive(0, length - 1);
}
//测试
public static void quickSortTest() {
int[] tempUnsortedKeys = { 1, 3, 12, 10, 5, 7, 9 };
String[] tempContents = { "if", "then", "else", "switch", "case", "for", "while" };
DataArray tempDataArray = new DataArray(tempUnsortedKeys, tempContents);
System.out.println(tempDataArray);
tempDataArray.quickSort();
System.out.println("Result\r\n" + tempDataArray);
}
Day47:选择排序
选择排序(Selection sort)是一种简单直观的排序。
它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
选择排序流程:
(1) 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
(2) 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
(3) 重复第二步,直到所有元素均排序完毕。
代码:
//选择排序
public void selectionSort() {
DataNode tempNode;
int tempIndexForSmallest;
for (int i = 0; i < length - 1; i++) {
// 初始化
tempNode = data[i];
tempIndexForSmallest = i;
for (int j = i + 1; j < length; j++) {
if (data[j].key < tempNode.key) {
tempNode = data[j];
tempIndexForSmallest = j;
}
}
// 将最小的元素与排位元素交换位置
data[tempIndexForSmallest] = data[i];
data[i] = tempNode;
}
}
public static void selectionSortTest() {
int[] tempUnsortedKeys = { 5, 3, 6, 10, 7, 1, 9 };
String[] tempContents = { "if", "then", "else", "switch", "case", "for", "while" };
DataArray tempDataArray = new DataArray(tempUnsortedKeys, tempContents);
System.out.println(tempDataArray);
tempDataArray.selectionSort();
System.out.println("Result\r\n" + tempDataArray);
}
Day48:堆排序
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
选择排序流程:
(1) 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
(2) 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
(3) 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
代码:
//堆排序
public void heapSort() {
DataNode tempNode;
// Step 1. 建立初始堆
for (int i = length / 2 - 1; i >= 0; i--) {
adjustHeap(i, length);
}
System.out.println("The initial heap: " + this + "\r\n");
// Step 2. 交换并重构堆
for (int i = length - 1; i > 0; i--) {
tempNode = data[0];
data[0] = data[i];
data[i] = tempNode;
adjustHeap(0, i);
System.out.println("Round " + (length - i) + ": " + this);
}
}
//调整堆
public void adjustHeap(int paraStart, int paraLength) {
DataNode tempNode = data[paraStart];
int tempParent = paraStart;
int tempKey = data[paraStart].key;
for (int tempChild = paraStart * 2 + 1; tempChild < paraLength; tempChild = tempChild * 2 + 1) {
// 右孩子更大
if (tempChild + 1 < paraLength) {
if (data[tempChild].key < data[tempChild + 1].key) {
tempChild++;
}
}
System.out.println("The parent position is " + tempParent + " and the child is " + tempChild);
if (tempKey < data[tempChild].key) {
//孩子更大
data[tempParent] = data[tempChild];
System.out.println("Move " + data[tempChild].key + " to position " + tempParent);
tempParent = tempChild;
} else {
break;
}
}
data[tempParent] = tempNode;
System.out.println("Adjust " + paraStart + " to " + paraLength + ": " + this);
}
public static void heapSortTest() {
int[] tempUnsortedKeys = { 5, 3, 6, 10, 7, 1, 9 };
String[] tempContents = { "if", "then", "else", "switch", "case", "for", "while" };
DataArray tempDataArray = new DataArray(tempUnsortedKeys, tempContents);
System.out.println(tempDataArray);
tempDataArray.heapSort();
System.out.println("Result\r\n" + tempDataArray);
}
Day49:归并排序
归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
选择排序流程:
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4.重复步骤3直到某一指针超出序列尾;将另一序列剩下的所有元素直接复制到合并序列尾。
代码:
//归并排序
public void mergeSort() {
// Step 1. 分配空间
int tempRow; // 当前行
int tempGroups; // 组数
int tempActualRow; // 只有0或1
int tempNextRow = 0;
int tempGroupNumber;
int tempFirstStart, tempSecondStart, tempSecondEnd;
int tempFirstIndex, tempSecondIndex;
int tempNumCopied;
for (int i = 0; i < length; i++) {
System.out.print(data[i]);
}
System.out.println();
DataNode[][] tempMatrix = new DataNode[2][length];
// Step 2. 复制数据
for (int i = 0; i < length; i++) {
tempMatrix[0][i] = data[i];
} // Of for i
// Step 3. 循环 log n 次
tempRow = -1;
for (int tempSize = 1; tempSize <= length; tempSize *= 2) {
// 重新利用两行的空间
tempRow++;
System.out.println("Current row = " + tempRow);
tempActualRow = tempRow % 2;
tempNextRow = (tempRow + 1) % 2;
tempGroups = length / (tempSize * 2);
if (length % (tempSize * 2) != 0) {
tempGroups++;
}
System.out.println("tempSize = " + tempSize + ", numGroups = " + tempGroups);
for (tempGroupNumber = 0; tempGroupNumber < tempGroups; tempGroupNumber++) {
tempFirstStart = tempGroupNumber * tempSize * 2;
tempSecondStart = tempGroupNumber * tempSize * 2 + tempSize;
if (tempSecondStart > length - 1) {
// 复制第一部分
for (int i = tempFirstStart; i < length; i++) {
tempMatrix[tempNextRow][i] = tempMatrix[tempActualRow][i];
}
continue;
}
tempSecondEnd = tempGroupNumber * tempSize * 2 + tempSize * 2 - 1;
if (tempSecondEnd > length - 1) {
tempSecondEnd = length - 1;
}
System.out
.println("Trying to merge [" + tempFirstStart + ", " + (tempSecondStart - 1)
+ "] with [" + tempSecondStart + ", " + tempSecondEnd + "]");
tempFirstIndex = tempFirstStart;
tempSecondIndex = tempSecondStart;
tempNumCopied = 0;
while ((tempFirstIndex <= tempSecondStart - 1)
&& (tempSecondIndex <= tempSecondEnd)) {
if (tempMatrix[tempActualRow][tempFirstIndex].key <= tempMatrix[tempActualRow][tempSecondIndex].key) {
tempMatrix[tempNextRow][tempFirstStart
+ tempNumCopied] = tempMatrix[tempActualRow][tempFirstIndex];
tempFirstIndex++;
System.out.println("copying " + tempMatrix[tempActualRow][tempFirstIndex]);
} else {
tempMatrix[tempNextRow][tempFirstStart
+ tempNumCopied] = tempMatrix[tempActualRow][tempSecondIndex];
System.out.println("copying " + tempMatrix[tempActualRow][tempSecondIndex]);
tempSecondIndex++;
}
tempNumCopied++;
}
while (tempFirstIndex <= tempSecondStart - 1) {
tempMatrix[tempNextRow][tempFirstStart
+ tempNumCopied] = tempMatrix[tempActualRow][tempFirstIndex];
tempFirstIndex++;
tempNumCopied++;
}
while (tempSecondIndex <= tempSecondEnd) {
tempMatrix[tempNextRow][tempFirstStart
+ tempNumCopied] = tempMatrix[tempActualRow][tempSecondIndex];
tempSecondIndex++;
tempNumCopied++;
}
}
System.out.println("Round " + tempRow);
for (int i = 0; i < length; i++) {
System.out.print(tempMatrix[tempNextRow][i] + " ");
}
System.out.println();
}
data = tempMatrix[tempNextRow];
}
//测试
public static void mergeSortTest() {
int[] tempUnsortedKeys = { 5, 3, 6, 10, 7, 1, 9 };
String[] tempContents = { "if", "then", "else", "switch", "case", "for", "while" };
DataArray tempDataArray = new DataArray(tempUnsortedKeys, tempContents);
System.out.println(tempDataArray);
tempDataArray.mergeSort();
System.out.println(tempDataArray);
}
Day60:小结
顺序查找
优点:适用于小型问题;缺点:对于大型符号表很慢。
折半查找
优点:比较次数少,查找速度快,平均性能好;缺点:要求待查表为有序表,且插入删除困难。
哈希表
优点:效率很高;缺点:需要设计相应的算法去处理冲突
一、冒泡排序
特点:效率低,实现简单
原理:将待排序列中最大的数往后冒泡,成为新的序列,重复以上操作直到所有元素排列完成
二、选择排序
特点:效率低,容易实现。
原理:每一趟从待排序序列选择一个最小的元素放到已排好序序列的首位,剩下的位待排序序列,重复上述步骤直到完成排序
三、插入排序
特点:效率低,容易实现。
原理:将数组分为两部分,将后部分元素逐一插入前部分有序元素的适当位置
四、快速排序
特点:高效,时间复杂度为nlogn。
原理:采用分治法的思想:首先设置一个中间值,然后以这个中间值为划分基准将待排序序列分成比该值大和比该值小的两部分,将这两部分再分别进行快速排序 直到序列只剩下一个元素
五、堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
六、归并排序
归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
七、希尔排序
设置一个步长(间隔),然后将表中的全部记录分成d组,每组进行直接插入排序,又取步长a<d(a=d/2),重复上述操作,直到步长=1
算法名称 | 时间复杂度 | 空间复杂度 |
---|---|---|
插入排序 | O(n2) | O(1) |
希尔排序 | O(n1.3)→O(n2) | O(1) |
快速排序 | O(nlog2n) | O(log2n) |
冒泡排序 | O(n2) | O(1) |
选择排序 | O(n2) | O(1) |
归并排序 | O(nlog2n) | O(n) |
堆排序 | O(nlog2n) | O(1) |