Java应用使用Java实现数据结构和算法:排序查找图

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java应用使用Java实现数据结构和算法:排序查找图相关的知识,希望对你有一定的参考价值。

如果您觉得本博客的内容对您有所帮助或启发,请关注我的博客,以便第一时间获取最新技术文章和教程。同时,也欢迎您在评论区留言,分享想法和建议。谢谢支持!

相关阅读:

​Java应用【一】Java文件操作:读写文件和文件夹​

​Java应用【二】Java 并发编程与任务调度详解​

​Java应用【三】使用Jackson库进行JSON序列化和反序列化​

​Java应用【四】如何使用JPA进行对象关系映射和持久化​

​Java应用【五】使用HttpClient进行HTTP请求和响应处理​

​Java应用【六】Java 反射:动态类加载和调用教程​


一、介绍

数据结构和算法是计算机科学的两个基石,它们是解决各种复杂问题和优化计算机程序的关键工具。数据结构和算法的重要性体现在以下几个方面:

  1. 提高程序效率:好的数据结构和算法可以让程序更快地运行,更有效地利用计算资源。例如,使用快速排序算法比使用冒泡排序算法可以大大减少排序时间。
  2. 解决复杂问题:数据结构和算法可以帮助开发人员解决各种复杂问题,如图形处理、人工智能、大数据处理等。例如,使用适当的数据结构和算法可以使搜索引擎更好地处理用户查询,并返回最相关的结果。
  3. 提高程序可靠性:使用正确的数据结构和算法可以避免程序运行时出现各种错误和异常。例如,在对数据进行操作时,如果使用了不适当的数据结构或算法,可能会导致数据损坏或程序崩溃。
  4. 更好地组织和管理数据:使用正确的数据结构可以更好地组织和管理数据。例如,使用哈希表可以使程序更快地查找数据,而使用树结构可以更好地组织层次化数据。

数据结构和算法是计算机科学中非常重要的工具,可以提高程序效率、解决复杂问题、提高程序可靠性和更好地组织和管理数据。掌握数据结构和算法对于开发高质量、高效的计算机程序是非常必要的。


二、排序算法

1、冒泡排序(Bubble Sort)

冒泡排序是一种基本的排序算法,它的思想是比较相邻的两个元素,如果前一个元素大于后一个元素,则交换这两个元素的位置,一轮比较后,最大的元素会被交换到数组的最后面。重复这个过程,直到整个数组都被排序。

以下是使用Java实现冒泡排序的代码:

import java.util.Arrays;

public class BubbleSort
public static void bubbleSort(int[] arr)
int n = arr.length;
// 外层循环控制比较轮数
for (int i = 0; i < n - 1; i++)
// 内层循环控制每轮比较次数
for (int j = 0; j < n - i - 1; j++)
// 如果前一个元素大于后一个元素,则交换位置
if (arr[j] > arr[j + 1])
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;





public static void main(String[] args)
int[] arr = 4, 2, 8, 3, 1, 9, 6, 5, 7 ;
bubbleSort(arr);
System.out.println(Arrays.toString(arr));

在上面的代码中,我们定义了一个静态方法​​bubbleSort​​来实现冒泡排序,它接受一个整型数组作为参数,用两个循环来实现冒泡排序的过程。在外层循环中,控制比较的轮数,循环次数为数组长度减1;在内层循环中,控制每轮比较的次数,循环次数为数组长度减去当前轮数减1。如果前一个元素大于后一个元素,则交换这两个元素的位置。

最后,在​​main​​​方法中调用​​bubbleSort​​​方法对一个整型数组进行排序,并使用​​Arrays.toString​​方法将排序后的结果输出到控制台。

2、选择排序(Selection Sort)

选择排序的思想是从数组中选择最小的元素,将其放在数组的最前面,然后从剩余的元素中再选择最小的元素,放在已排序部分的最后面。重复这个过程,直到整个数组都被排序。

以下是使用Java实现选择排序的代码:

import java.util.Arrays;

public class SelectionSort
public static void selectionSort(int[] arr)
int n = arr.length;
// 外层循环控制已排序部分的长度
for (int i = 0; i < n - 1; i++)
int minIndex = i;
// 内层循环从未排序部分中选出最小元素的下标
for (int j = i + 1; j < n; j++)
if (arr[j] < arr[minIndex])
minIndex = j;


// 将最小元素与已排序部分的末尾元素交换位置
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;



public static void main(String[] args)
int[] arr = 4, 2, 8, 3, 1, 9, 6, 5, 7 ;
selectionSort(arr);
System.out.println(Arrays.toString(arr));

在上面的代码中,我们定义了一个静态方法​​selectionSort​​来实现选择排序,它接受一个整型数组作为参数,用两个循环来实现选择排序的过程。在外层循环中,控制已排序部分的长度,循环次数为数组长度减1;在内层循环中,从未排序部分中选出最小元素的下标。如果找到了一个更小的元素,则将其下标更新为最小元素的下标。

最后,在​​main​​​方法中调用​​selectionSort​​​方法对一个整型数组进行排序,并使用​​Arrays.toString​​方法将排序后的结果输出到控制台。

3、插入排序(Insertion Sort)

插入排序的思想是将数组分为已排序和未排序两部分,每次将未排序的元素插入到已排序部分的适当位置。具体操作是从未排序部分取出第一个元素,从已排序部分的最后一个元素开始向前比较,直到找到比它小的元素为止,然后将该元素插入到合适位置。

以下是使用Java实现插入排序的代码:

import java.util.Arrays;

public class InsertionSort
public static void insertionSort(int[] arr)
int n = arr.length;
// 外层循环控制已排序部分的长度
for (int i = 1; i < n; i++)
int cur = arr[i];
int j = i - 1;
// 内层循环将待排序元素插入到已排序部分的适当位置
while (j >= 0 && arr[j] > cur)
arr[j + 1] = arr[j];
j--;

arr[j + 1] = cur;



public static void main(String[] args)
int[] arr = 4, 2, 8, 3, 1, 9, 6, 5, 7 ;
insertionSort(arr);
System.out.println(Arrays.toString(arr));

在上面的代码中,我们定义了一个静态方法​​insertionSort​​来实现插入排序,它接受一个整型数组作为参数,用两个循环来实现插入排序的过程。在外层循环中,控制已排序部分的长度,循环次数为数组长度减1;在内层循环中,将待排序元素插入到已排序部分的适当位置。将待排序元素与已排序部分中的元素从后往前依次比较,如果已排序部分中的元素比待排序元素大,则将该元素向后移动一个位置,直到找到待排序元素的正确位置。

最后,在​​main​​​方法中调用​​insertionSort​​​方法对一个整型数组进行排序,并使用​​Arrays.toString​​方法将排序后的结果输出到控制台。

4、快速排序(Quick Sort)

快速排序是一种分治思想的排序算法,它的核心思想是将数组分成两部分,一部分比另一部分小,然后对这两部分分别进行快速排序,最后合并起来。具体操作是选择一个基准元素,将数组分成两部分,左边是比基准元素小的元素,右边是比基准元素大的元素,然后递归地对左右两部分进行快速排序。

快速排序是一种高效的排序算法,它的基本思想是选定一个基准元素,将待排序部分分成左右两个子序列,使得左子序列中的元素都小于基准元素,右子序列中的元素都大于等于基准元素。然后对左右子序列递归地进行快速排序,直到整个序列都有序。

以下是使用Java实现快速排序的代码:

import java.util.Arrays;

public class QuickSort
public static void quickSort(int[] arr, int left, int right)
if (left < right)
// 找到基准元素的位置
int pivotIndex = partition(arr, left, right);
// 对左右子序列递归地进行快速排序
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);



private static int partition(int[] arr, int left, int right)
// 将第一个元素作为基准元素
int pivot = arr[left];
int i = left + 1;
int j = right;
while (true)
// 从左往右找到第一个大于等于基准元素的位置
while (i <= j && arr[i] < pivot)
i++;

// 从右往左找到第一个小于基准元素的位置
while (i <= j && arr[j] >= pivot)
j--;

if (i >= j)
// 如果i >= j,则说明已经将数组分成了左右两个子序列
// 左子序列中的元素都小于基准元素,右子序列中的元素都大于等于基准元素
break;

// 交换arr[i]和arr[j]的位置,将小于等于基准元素的元素放到左子序列中
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
// 继续在左子序列中找大于等于基准元素的元素
i++;
// 在右子序列中找小于基准元素的元素
j--;

// 将基准元素放到正确的位置,即i-1的位置
arr[left] = arr[i - 1];
arr[i - 1] = pivot;
// 返回基准元素的位置
return i - 1;


public static void main(String[] args)
int[] arr = 4, 2, 8, 3, 1, 9, 6, 5, 7 ;
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));

在上面的代码中,我们定义了一个静态方法​​quickSort​​来实现快速排序,它接受一个整型数组、左边界和右边界作为参数,用递归的方式实现快速排序的过程。在每一轮快速排序中,我们首先选定一个基准元素,然后使用两个指针i和j,分别指向左右两端。然后,我们从左往右遍历数组,找到第一个大于等于基准元素的元素的位置i,再从右往左遍历数组,找到第一个小于基准元素的元素的位置j。如果i和j没有相遇,就交换arr[i]和arr[j]的位置,将小于等于基准元素的元素放到左子序列中。继续在左子序列中找大于等于基准元素的元素,直到i >= j,说明已经将数组分成了左右两个子序列。然后,将基准元素放到正确的位置,即i-1的位置,左子序列中的元素都小于基准元素,右子序列中的元素都大于等于基准元素。

快速排序的时间复杂度为O(nlogn),最坏情况下的时间复杂度为O(n^2),但是这种情况比较少见。它的空间复杂度为O(logn),它是原地排序算法,不需要额外的空间。

下面是使用上面的代码对一个整型数组进行快速排序的示例输出:

[1, 2, 3, 4, 5, 6, 7, 8, 9]


5、归并排序(Merge Sort)

归并排序是一种基于分治思想的排序算法,它的核心思想是将一个大问题分解成若干个小问题进行解决,然后将小问题的解合并起来,得到原问题的解。归并排序的基本思路是将待排序的序列分成若干个子序列,每个子序列都是有序的,然后再将子序列之间进行合并,得到完全有序的序列。

归并排序适用于大规模数据的排序,其时间复杂度稳定在O(nlogn)。它的主要优点是稳定性好,可以处理大规模数据,但是它需要额外的空间来存储分治时产生的中间结果。

下面是使用Java实现归并排序的示例代码:

import java.util.Arrays;

public class MergeSort

public static void mergeSort(int[] arr)
int[] temp = new int[arr.length];
sort(arr, 0, arr.length - 1, temp);


private static void sort(int[] arr, int left, int right, int[] temp)
if (left < right)
int mid = (left + right) / 2;
sort(arr, left, mid, temp);
sort(arr, mid + 1, right, temp);
merge(arr, left, mid, right, temp);



private static void merge(int[] arr, int left, int mid, int right, int[] temp)
int i = left;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= right)
if (arr[i] <= arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];


while (i <= mid)
temp[k++] = arr[i++];

while (j <= right)
temp[k++] = arr[j++];

k = 0;
while (left <= right)
arr[left++] = temp[k++];



public static void main(String[] args)
int[] arr = 9, 8, 7, 6, 5, 4, 3, 2, 1 ;
mergeSort(arr);
System.out.println(Arrays.toString(arr));

在归并排序中,最重要的步骤就是合并两个有序子序列。当我们在合并两个有序子序列时,我们可以通过双指针遍历的方式,将两个子序列中的元素按照大小依次放入一个临时数组中,直到其中一个子序列的元素全部被放入临时数组中。此时,另一个子序列中的元素肯定都比临时数组中的元素大(因为它们是有序的),因此我们可以直接将这些元素放到临时数组的末尾。

最后,我们只需要将临时数组中的元素复制回原数组中,这样就完成了归并排序的整个过程。

在实际应用中,归并排序常常被用作内部排序算法,例如Java中的Arrays.sort方法就是使用了归并排序。


6、堆排序(Heap sort)

堆排序是一种基于堆数据结构的排序算法,它的思想是先将待排序的数组构建成一个二叉堆,然后依次将堆顶元素(即最大或最小元素)取出,放到已排序部分的末尾,然后重新调整剩余元素的堆结构,使之满足堆的性质。重复这个过程,直到所有元素都被排序。

堆排序可以分为两个步骤:


  1. 构建堆:将待排序的数组构建成一个二叉堆。具体操作是从数组的最后一个非叶子节点开始,依次将其与其子节点进行比较,如果不满足堆的性质,则交换两个节点的位置,然后向前移动,继续比较,直到根节点。
  2. 排序:依次将堆顶元素取出,放到已排序部分的末尾,然后重新调整剩余元素的堆结构,使之满足堆的性质。具体操作是将堆顶元素和堆的最后一个元素交换位置,然后将堆的大小减1,再对堆顶元素进行下滤操作,将其移动到合适的位置,使之满足堆的性质。

堆排序的时间复杂度为O(nlogn),空间复杂度为O(1),它是一种原地排序算法,不需要额外的空间存储数据。堆排序适用于需要排序大量数据且对空间复杂度有限制的场景,但由于堆排序的常数较大,相比于快速排序等算法,在小规模数据上的效率可能不如其他算法。

下面是一个使用Java实现的堆排序的例子:

import java.util.Arrays;

public class HeapSort
public static void sort(int[] arr)
int n = arr.length;

// 构建堆
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);


// 堆排序
for (int i = n - 1; i >= 0; i--)
// 将堆顶元素与末尾元素交换
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;

// 对剩余元素重新构建堆
heapify(arr, i, 0);



// 堆化操作
private static void heapify(int[] arr, int n, int i)
int largest = i; // 初始化最大值为根节点
int l = 2 * i + 1; // 左孩子节点
int r = 2 * i + 2; // 右孩子节点

// 找出三个节点中的最大值
if (l < n && arr[l] > arr[largest])
largest = l;

if (r < n && arr[r] > arr[largest])
largest = r;


// 如果最大值不是根节点,则交换根节点和最大值,并继续向下堆化
if (largest != i)
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);



public static void main(String[] args)
int[] arr = 9, 8, 7, 6, 5, 4, 3, 2, 1 ;
sort(arr);
System.out.println(Arrays.toString(arr));

在这个例子中,我们先使用​​heapify​​方法将数组构建成一个最大堆,然后在每一轮排序中将堆顶元素和数组末尾元素交换位置,并重新构建堆。最终,我们就可以得到一个有序的数组。由于堆排序的时间复杂度为O(nlogn),因此它是一种非常高效的排序算法,常常被用于大规模数据的排序。

7、小结

冒泡排序和选择排序是最简单的排序算法,虽然它们的时间复杂度较高,但是它们易于理解和实现。插入排序的时间复杂度略低于冒泡排序和选择排序,但是由于它具有稳定性和较好的性能,因此它在实际应用中比较常见。

快速排序和归并排序是更高效的排序算法,它们的时间复杂度为O(nlogn),因此它们在处理大规模数据时具有优势。快速排序通常比归并排序更快,但是它的稳定性较差。相比之下,归并排序的稳定性较好,适用于更多的场景。

堆排序是一种时间复杂度为O(nlogn)的排序算法,它的性能比冒泡排序、选择排序和插入排序都要好,但是在实际应用中常常被归并排序和快速排序所取代。

在实际应用中,我们需要根据具体的问题和数据特征选择不同的排序算法。对于小规模数据,我们可以使用冒泡排序、选择排序、插入排序等简单的排序算法;对于大规模数据,我们可以使用快速排序、归并排序、堆排序等更高效的排序算法。


三、查找算法

1、线性查找

线性查找,也称为顺序查找,是一种基本的查找算法。它的思路是从数据集合的第一个元素开始逐个向后查找,直到找到目标元素或遍历完整个数据集合为止。因为是逐个比较,所以适用于各种数据类型,包括无序和有序的数据集合。

线性查找的时间复杂度为 O(n),其中 n 表示数据集合的大小,最坏情况下需要查找整个数据集合。但如果数据集合有序,可以通过优化跳出循环来减少比较次数,此时时间复杂度可达到 O(1)。

以下是使用 Java 实现线性查找的示例代码:

public class LinearSearch 
public static int linearSearch(int[] arr, int target)
for (int i = 0; i < arr.length; i++)
if (arr[i] == target)
return i;


return -1;


public static void main(String[] args)
int[] arr = 1, 2, 3, 4, 5;
int target = 3;
int index = linearSearch(arr, target);
if (index != -1)
System.out.println("目标元素 " + target + " 在数组中的位置为:" + index);
else
System.out.println("目标元素 " + target + " 不在数组中");


Java应用【七】使用Java实现数据结构和算法:排序、查找、图_图

上面的代码实现了一个简单的线性查找函数 ​​linearSearch​​​,并在 ​​main​​ 函数中演示了如何使用该函数查找数组中的元素。


2、二分查找

二分查找,也称为折半查找,是一种常用的查找算法。它的思路是针对有序的数据集合,在每次查找过程中都将待查找区间缩小一半,直到找到目标元素或者待查找区间为空为止。

二分查找的时间复杂度为 O(log n),其中 n 表示数据集合的大小。由于每次查找都会将待查找区间缩小一半,因此它的效率比线性查找高很多,特别是对于大型有序数据集合。但是它的局限性也很明显,只适用于有序数据集合。

以下是使用 Java 实现二分查找的示例代码:

public class BinarySearch 
public static int binarySearch(int[] arr, int target)
int left = 0;
int right = arr.length - 1;
while (left <= right)
int mid = (left + right) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;


return -1;


public static void main(String[] args)
int[] arr = 1, 2, 3, 4, 5;
int target = 3;
int index = binarySearch(arr, target);
if (index != -1)
System.out.println("目标元素 " + target + " 在数组中的位置为:" + index);
else
System.out.println("目标元素 " + target + " 不在数组中");


上面的代码实现了一个简单的二分查找函数 ​​binarySearch​​​,并在 ​​main​​ 函数中演示了如何使用该函数查找数组中的元素。

Java应用【七】使用Java实现数据结构和算法:排序、查找、图_排序算法_02

3、插值查找

插值查找是二分查找的一种变体,也适用于有序数据集。与二分查找不同的是,插值查找根据目标值的估计位置来选择查找区间,而不是直接选择中间位置。

具体而言,插值查找算法的基本思想是将目标值与查找区间的两个端点相比较,然后根据目标值在整个数据集中的相对位置,计算出一个估计位置。接着,根据该估计位置来缩小查找区间,直到找到目标值为止。

插值查找算法的时间复杂度为O(logn),但是对于数据分布比较均匀的数据集,插值查找的效率可能会比二分查找更高。但是对于极端数据分布情况,插值查找的性能可能会下降。

下面是插值查找的Java代码实现示例:

public class InterpolationSearch 
public static int interpolationSearch(int[] arr, int target)
int left = 0, right = arr.length - 1;
while (left <= right && target >= arr[left] && target <= arr[right])
int pos = left + ((target - arr[left]) * (right - left)) / (arr[right] - arr[left]);
if (arr[pos] == target)
return pos;

if (arr[pos] < target)
left = pos + 1;
else
right = pos - 1;


return -1;

public static void main(String[] args)
int[] arr = 1, 2, 3, 4, 5;
int target = 5;
int index = interpolationSearch(arr, target);
if (index != -1)
System.out.println("目标元素 " + target + " 在数组中的位置为:" + index);
else
System.out.println("目标元素 " + target + " 不在数组中");


在该示例中,我们首先计算出估计位置pos,然后根据pos与target的大小关系来缩小查找范围。具体而言,如果arr[pos] < target,则将left赋值为pos + 1;如果arr[pos] > target,则将right赋值为pos - 1。如果arr[pos] = target,则返回pos。

Java应用【七】使用Java实现数据结构和算法:排序、查找、图_查找算法_03

4、斐波那契查找

斐波那契查找是一种基于黄金分割点的查找算法,可以用于有序数据集的查找。与二分查找类似,斐波那契查找也是将目标值与查找区间的中间点进行比较,然后根据比较结果来缩小查找范围。

不同的是,斐波那契查找使用了黄金分割点的概念,即将查找区间按照一定比例分成两个子区间。具体而言,假设查找区间的长度为n,我们先在斐波那契数列中找到一个比n大且最接近n的数f,然后将查找区间分成长度为f和f-1的两个子区间。接着,将目标值与两个子区间的中间点进行比较,然后根据比较结果来进一步缩小查找范围。

斐波那契查找算法的时间复杂度为O(logn),但是相对于二分查找,它需要额外的空间来存储斐波那契数列。

斐波那契查找适用于有序数据集,且数据集长度不确定或数据集分布较为均匀的情况。由于斐波那契数列的增长速度比二分查找快,因此在查找区间长度比较大的情况下,斐波那契

以上是关于Java应用使用Java实现数据结构和算法:排序查找图的主要内容,如果未能解决你的问题,请参考以下文章

排序算法入门之归并排序(java实现)

数据结构-排序之基数排序(使用java代码实现)

数据结构-排序之基数排序(使用java代码实现)

数据结构-排序之基数排序(使用java代码实现)

排序算法之冒泡选择插入排序(Java)

排序算法之冒泡选择插入排序(Java)