实验4:查找与排序综合应用
采用二分查找的方法实现查找 (1)定义顺序表的存储结构; (2)实现顺序表上二分查找; 采用二叉排序树实现查找 (1)定义二叉链表的存储结构; (2)实现二叉排序树的建立、查找、插入和删除结点操作; 冒泡排序 (1)定义顺序表的存储结构; (2)在顺序表上实现冒泡排序; (3)将普通的冒泡排序进行多次改进以提高排序速度,并用大量数据测试其速度的提高。 快速排序 (1)定义顺序表的存储结构; (2)在顺序表上实现快速排序; (3)用大量的数据测试最好、最坏和平均情况下的排序速度。 堆排序 (1)定义顺序表的存储结构; (2)在顺序表上实现堆排序; (3)用大量的数据测试最好、最坏和平均情况下的排序速度。 比较各种排序方法的优缺点和适用场合
1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#define bug(BUG) std::cout << "bug::" << BUG << std::endl;
#define bug2(BUG1, BUG2) std::cout << "bug::" << BUG1 << ' ' << BUG2 << std::endl;
constexpr int MaxSize = 1000, mod = MaxSize;struct List
{int data[MaxSize];int len;
};void BinarySearch(List &list, const int tar)
{int l = -1, r = list.len - 1;while (r - l - 1){int mid = l + r >> 1;if (list.data[mid] >= tar)r = mid;elsel = mid;}if (list.data[r] == tar)printf("%d的下标为%d\n", tar, r);elseprintf("未找到%d\n", tar);
}
int main()
{int n; // 元素个数scanf("%d", &n);List list;list.len = n;// 读入有序表for (int i = 0; i < n; i++)scanf("%d", &list.data[i]);puts("有序表:>_<");for (int i = 0; i < list.len; i++)printf("%d ", list.data[i]);puts("");BinarySearch(list, 1);BinarySearch(list, 6);BinarySearch(list, 12);BinarySearch(list, 20);BinarySearch(list, 50);BinarySearch(list, 128);BinarySearch(list, 256);BinarySearch(list, 512);BinarySearch(list, 1024);BinarySearch(list, 4096);
}// 测试数据
/*
20
1 2 3 4 5 6 10 12 18 29
49 40 60 64 128 256 512 1024 2048 4096
*/// 测试结果
/*
有序表:>_<
1 2 3 4 5 6 10 12 18 29 49 40 60 64 128 256 512 1024 2048 4096
1的下标为0
6的下标为5
12的下标为7
未找到20
未找到50
128的下标为14
256的下标为15
512的下标为16
1024的下标为17
4096的下标为19*/
2
#include <stdio.h>
#include <stdlib.h>// 二叉链表节点结构
typedef struct Node
{int value; // 节点存储的值struct Node *left; // 指向左子节点的指针struct Node *right; // 指向右子节点的指针
} Node;Node *createNode(int value)
{Node *newNode = (Node *)malloc(sizeof(Node));newNode->value = value;newNode->left = NULL;newNode->right = NULL;return newNode;
}Node *insert(Node *root, int value)
{if (root == NULL)return createNode(value);if (value < root->value) // 小于当前节点值,插入左子树root->left = insert(root->left, value);else // 大于或等于当前节点值,插入右子树root->right = insert(root->right, value);return root;
}Node *search(Node *root, int value)
{if (root == NULL || root->value == value)return root; // 找到节点或树为空if (value < root->value) // 在左子树中查找return search(root->left, value);else // 在右子树中查找return search(root->right, value);
}Node *findMin(Node *root) // 找到最小节点
{while (root->left != NULL)root = root->left;return root;
}Node *deleteNode(Node *root, int value)
{if (root == NULL)return root;if (value < root->value) // 在左子树中删除root->left = deleteNode(root->left, value);else if (value > root->value) // 在右子树中删除root->right = deleteNode(root->right, value);else{if (root->left == NULL) // 只有右子树{Node *temp = root->right;free(root);return temp;}else if (root->right == NULL) // 只有左子树{Node *temp = root->left;free(root);return temp;}// 节点有两个子树,找到右子树的最小值Node *temp = findMin(root->right);root->value = temp->value; // 用右子树的最小值替代当前节点root->right = deleteNode(root->right, temp->value); // 删除右子树中的最小值}return root;
}int main()
{Node *root = NULL;root = insert(root, 50);root = insert(root, 30);root = insert(root, 20);root = insert(root, 40);root = insert(root, 70);root = insert(root, 60);root = insert(root, 80);root = deleteNode(root, 20);Node *found = search(root, 30);if (found)printf("找到节点: %d\n", found->value);elseprintf("未找到节点 30\n");return 0;
}
3
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#define bug(BUG) std::cout << "bug::" << BUG << std::endl;
#define bug2(BUG1, BUG2) std::cout << "bug::" << BUG1 << ' ' << BUG2 << std::endl;
constexpr int MaxSize = 30000, mod = MaxSize;struct List
{int data[MaxSize];int len;
};void swap(int &a, int &b, int &cnt)
{a = a ^ b, b = a ^ b, a = a ^ b;cnt += 3;
}
void BubbleSort(List list)
{int cnt = 0;for (int i = 0; i < list.len - 1; i++)for (int j = list.len - 1; j > 0; j--){cnt++;if (list.data[j - 1] > list.data[j])swap(list.data[j - 1], list.data[j], cnt);}printf("BubbleSort的运算次数:%d\n运算结果:_< ", cnt);// for (int i = 0; i < list.len; i++)// printf("%d ", list.data[i]);puts("");
}// 改进的冒泡排序
void BubbleSortTurbo(List list)
{int cnt = 0;for (int i = 0; i < list.len - 1; i++){bool is_sorted = 1;for (int j = list.len - 1; j > i; j--){cnt++;if (list.data[j - 1] > list.data[j])swap(list.data[j - 1], list.data[j], cnt), is_sorted = 0;}if (is_sorted)break;}printf("改进后BubbleSort的运算次数:%d\n运算结果:_<", cnt);// for (int i = 0; i < list.len; i++)// printf("%d ", list.data[i]);puts("");
}int main()
{int n;scanf("%d", &n);List list;list.len = n;for (int i = 0; i < n; i++)scanf("%d", &list.data[i]);BubbleSort(list);// BubbleSortTurbo(list);
}// 数据与结果// 10
// 4 5 6 2 3 4 5 6 7 0
// BubbleSort的运算次数:99
// 运算结果:_< 0 2 3 4 4 5 5 6 6 7
// 改进后BubbleSort的运算次数:57
// 运算结果:_< 0 2 3 4 4 5 5 6 6 7// 1000个随机数
// BubbleSort的运算次数:1746855
// 改进后BubbleSort的运算次数:1247273// 5000个随机数
// BubbleSort的运算次数:43820554
// 改进后BubbleSort的运算次数:31324048// 10000个随机数
// BubbleSort的运算次数:175330872
// 改进后BubbleSort的运算次数:125328293// 30000个随机数
// BubbleSort的运算次数:1576654462 平均用时:1823ms
// 改进后BubbleSort的运算次数:1126694511 平均用时:1460ms
4
#include <stdio.h>
#include <stdlib.h>
#include <time.h>typedef struct
{int *data; // 指向数据的指针int size; // 当前元素的个数
} SequentialList;// 创建顺序表
SequentialList *createList(int capacity)
{SequentialList *list = (SequentialList *)malloc(sizeof(SequentialList));list->data = (int *)malloc(capacity * sizeof(int));list->size = 0;return list;
}void freeList(SequentialList *list)
{if (list){free(list->data);free(list);}
}void swap(int *a, int *b)
{int temp = *a;*a = *b;*b = temp;
}// 划分函数
int partition(int *arr, int low, int high)
{int pivot = arr[high]; // 选择最后一个元素作为支点int i = low - 1; // 分区指针for (int j = low; j < high; j++){if (arr[j] < pivot){ // 当前元素小于支点i++;swap(&arr[i], &arr[j]); // 交换}}swap(&arr[i + 1], &arr[high]); // 将支点放到正确位置return i + 1; // 返回支点索引
}// 快速排序的递归实现
void quickSort(int *arr, int low, int high)
{if (low < high){int pi = partition(arr, low, high); // 划分quickSort(arr, low, pi - 1); // 递归排序左半部分quickSort(arr, pi + 1, high); // 递归排序右半部分}
}// 快速排序的入口
void sort(SequentialList *list)
{if (list->size > 0){quickSort(list->data, 0, list->size - 1);}
}// 生成随机数据
void generateRandomData(SequentialList *list, int size)
{list->size = size;for (int i = 0; i < size; i++){list->data[i] = rand() % 100000;}
}// 测试排序速度
void testSort(int dataSize)
{SequentialList *list = createList(dataSize);generateRandomData(list, dataSize);clock_t start = clock();sort(list);clock_t end = clock();double timeSpent = (double)(end - start) / CLOCKS_PER_SEC;printf("排序 %d 个元素耗时: %.6f 秒\n", dataSize, timeSpent);freeList(list);
}int main()
{srand((unsigned)time(NULL));// 测试不同规模的数据testSort(100000); // 测试100000个元素testSort(1000000); // 测试1000000个元素testSort(10000000); // 测试10000000个元素return 0;
}// 测试结果// 排序 100000 个元素耗时: 0.006000 秒
// 排序 1000000 个元素耗时: 0.093000 秒
// 排序 10000000 个元素耗时: 1.619000 秒
5
#include <stdio.h>
#include <stdlib.h>
#include <time.h>typedef struct
{int *data; // 指向数据的指针int size; // 当前元素数量
} SequentialList;// 创建顺序表
SequentialList *createList(int capacity)
{SequentialList *list = (SequentialList *)malloc(sizeof(SequentialList));list->data = (int *)malloc(capacity * sizeof(int));list->size = 0;return list;
}void freeList(SequentialList *list)
{if (list){free(list->data);free(list);}
}// 交换两个元素
void swap(int &a, int &b)
{a = a ^ b, b = a ^ b, a = a ^ b;
}// 构建最大堆
void heapify(int *arr, int n, int i)
{int largest = i; // 初始化最大的元素为根int left = 2 * i + 1; // 左子节点int right = 2 * i + 2; // 右子节点// 如果左子节点比根节点大if (left < n && arr[left] > arr[largest])largest = left;// 如果右子节点比当前最大值还大if (right < n && arr[right] > arr[largest])largest = right;// 如果最大的不是根if (largest != i){swap(arr[i], arr[largest]); // 交换heapify(arr, n, largest); // 递归堆化}
}// 堆排序的主要函数
void heapSort(int *arr, int n)
{// 构建最大堆for (int i = n / 2 - 1; i >= 0; i--)heapify(arr, n, i);// 提取元素从堆中,进行排序for (int i = n - 1; i > 0; i--){swap(arr[0], arr[i]); // 将当前最大值交换到数组末尾heapify(arr, i, 0); // 重新调整堆}
}// 堆排序的接口
void sort(SequentialList *list)
{if (list->size > 0){heapSort(list->data, list->size);}
}// 生成随机数据
void generateRandomData(SequentialList *list, int size)
{list->size = size;for (int i = 0; i < size; i++){list->data[i] = rand() % 10000000;}
}// 测试排序速度
void testSort(int dataSize)
{SequentialList *list = createList(dataSize);generateRandomData(list, dataSize);clock_t start = clock();sort(list);clock_t end = clock();double timeSpent = (double)(end - start) / CLOCKS_PER_SEC;printf("排序 %d 个元素耗时: %.6f 秒\n", dataSize, timeSpent);freeList(list);
}int main()
{srand((unsigned)time(NULL));// 测试不同规模的数据testSort(100000); // 测试100000个元素testSort(1000000); // 测试1000000个元素testSort(5000000); // 测试5000000个元素return 0;
}// 测试结果// 排序 100000 个元素耗时: 0.014000 秒
// 排序 1000000 个元素耗时: 0.174000 秒
// 排序 5000000 个元素耗时: 1.203000 秒
6
// 1. 冒泡排序 (Bubble Sort)
// 优点:// 实现简单、容易理解。
// 对于小规模数据集或部分有序的数据集合效果较好;如果数据基本有序,冒泡排序仍能保持 O(n) 的时间复杂度。
// 缺点:// 平均和最坏情况时间复杂度是 O(n²),对于大规模数据集效率较低。
// 不适合大规模数据集。
// 适用场合:// 学习和教学目的,展示排序算法的基本思想。
// 小规模或几乎有序的数据集。
// 2. 选择排序 (Selection Sort)
// 优点:// 实现简单,操作简单。
// 不需要额外的内存,空间复杂度为 O(1)。
// 缺点:// 平均和最坏情况时间复杂度是 O(n²),对于大规模数据集效率不高。
// 效率比冒泡排序和插入排序略差,特别是在数据量增大时。
// 适用场合:// 数据量较小时可用,或内存资源有限的情况。
// 3. 插入排序 (Insertion Sort)
// 优点:// 当数据集比较小且几乎有序时,效率较高,时间复杂度可以达到 O(n)。
// 实现简单,且是稳定的排序算法。
// 不需要额外的空间,空间复杂度为 O(1)。
// 缺点:// 平均和最坏情况时间复杂度为 O(n²),不适合大规模无序数据。
// 适用场合:// 小规模数据、部分有序的数据集,或合并多个已经排序的序列。
// 4. 希尔排序 (Shell Sort)
// 优点:// 相比于简单的 O(n²) 的排序算法更高效,特别是对于中等规模的数据集。
// 适用于大规模数据排序,有较好的性能。
// 缺点:// 实现比简单排序复杂。
// 最坏时间复杂度是不定的,视增量序列而定,常见的增量序列使其最坏情况时间复杂度为 O(n²)。
// 适用场合:// 中等规模的数据集,数据并不完全有序的情况。
// 5. 归并排序 (Merge Sort)
// 优点:// 时间复杂度为 O(n log n),在最坏情况下仍然保持稳定。
// 稳定的排序算法,适合链表等数据结构。
// 当数据量非常大时,可以使用外部排序(如文件排序)。
// 缺点:// 需要 O(n) 的额外空间,内存开销较大。
// 实现相对复杂。
// 适用场合:// 数据量大、需要稳定性,或是数据在磁盘等外部存储中的情况。
// 6. 快速排序 (Quick Sort)
// 优点:// 平均时间复杂度为 O(n log n),在大多数情况下表现优秀。
// 适合大数据集,且空间复杂度较低(O(log n))。
// 缺点:// 最坏情况下(如已经排序的列表)时间复杂度为 O(n²),但通过随机化或优化可以减少这种情况的发生。
// 不稳定的排序算法。
// 适用场合:// 大规模数据排序,一般情况下使用最广泛的排序算法。
// 7. 堆排序 (Heap Sort)
// 优点:// 时间复杂度为 O(n log n),并且在最坏情况下保证此性能。
// 不需要额外的内存,空间复杂度为 O(1)。
// 缺点:// 不是稳定的排序算法。
// 相比于快速排序和归并排序,性能可能稍差。
// 适用场合:// 数据量较大时,是一种不错的选择,尤其是在对内存使用有严格限制的场合。
// 8. 计数排序 (Counting Sort)
// 优点:// 时间复杂度为 O(n + k),其中 k 是数据范围,效率非常高。
// 是稳定的排序算法。
// 缺点:// 只能用于整数或可整形的数据,适用范围有限。
// 需要 O(k) 的额外空间。
// 适用场合:// 数量较少且范围已知的整数集合,适合对特定范围内的整数进行排序。
// 9. 基数排序 (Radix Sort)
// 优点:// 对于数字或字母,效率很高,时间复杂度为 O(nk),k 是数字的位数。
// 稳定的排序算法。
// 缺点:// 对于小范围的整数排序效率不佳,空间复杂度也较高。
// 适用场合:// 适合对整数字或字符串等多位数据进行排序。
// 总结
// 选择排序算法时,需考虑数据规模、数据特点、时间和空间复杂度需求以及稳定性要求等因素。对于小规模数据,简单排序算法可能更合适;而对于大数据集,快速排序和归并排序通常是更好的选择。选择合适的排序算法可以更有效地提高程序性能。