任务2
已知一张图片是对某个事物横截面的扫描结果图,该图片的宽度是 m,高度是 n,图片的每一个像素只会由两种颜色之一构成:要么是蓝色,要么是白色。图片中的每一列的颜色分布有如下两种情形:
① 整列所有像素的颜色全是白色;
② 列中像素可以是白色或者蓝色,但在这种情况下,要么所有蓝色像素集中在从上到下,要么所有蓝色像素集中在从下到上,也就是说不会出现蓝色和白色相间的情形。
如果定义每一列的长度为蓝色像素的数量,那么如何求解图片中长度最大的列的长度呢?朴素的算法的时间复杂度是 O(mn),但该任务要求完成的算法的时间复杂度必须满足 O(mlogn).
在随实验的附件中有一个 tomography.png 图片,同学们可以使用这张图片做为测试数据,图片的大小是 1200*1600,该图片中最大列长是 1575。对图片的处理可以继续使用在面向对象程序设计课程中构建的 Picture 类型。
题目要求算法时间复杂度是O(mlogn),可以这样理解:
将每一列视为一个数组,数组是有序的,只含有1和0两种数字,分别代表蓝色和白色,对于每一列,也就是每一个数组,需要找到它含1的个数,使用二分查找尤为方便,二分查找的时间复杂度是O(logn),这样总体的时间复杂度就是O(mlogn)。
需要注意的是,数组的前半部分可能是1,也可能是0,需要分类讨论一下。
主要的时间复杂度来源于maxRow方法中的循环,该循环遍历图像的每一列。对于每一列,都会调用getNumberOfWhite或getNumberOfBlue方法进行二分查找。
在getNumberOfWhite和getNumberOfBlue方法中,使用了二分查找来确定每一列中连续的白色或蓝色像素的数量。在最坏情况下,二分查找的时间复杂度为O(logn),其中n是图像的高度。
因此,整个算法的时间复杂度是O(mlogn),其中m是图像的宽度,n是图像的高度。
maxRow方法:public方法,供外界调用。
- public int maxRow(Picture picture) {
- //用1代表蓝色,0代表白色
- int max = 0;
- //查找每一列的蓝色或者白色值
- for (int w = 0; w < picture.getWidth(); w++) {
- //查找蓝色还是查找白色
- int flag = picture.test(picture, w);
- //每一列用二分查找
- //照片最上面可能是蓝色,也可能是白色,如果是蓝色,那么就查蓝色的数量
- if (flag == 0) {//白色
- int number;
- number = getNumberOfWhite(0, picture.getHeight() - 1, w, picture);
- if (number > max) {
- max = picture.getHeight() - number;
- }
- } else if (flag == 1) {//蓝色
- int number;
- number = getNumberOfBlue(0, picture.getHeight() - 1, w, picture);
- if (number > max) {
- max = number;
- }
- }
- }
- return max;
- }
Test方法:测图片每一列第一个像素是蓝色还是白色。
- //仅仅是测试开始的
- private int test(Picture picture, int width) {
- if (picture != null && picture.getWidth() > 0 && picture.getHeight() > 0) {
- Color color = picture.getColor(width, 0);
- if (color != null) {
- if (color.equals(Color.BLUE)) {
- return 1;
- } else if (color.equals(Color.WHITE)) {
- return 0;
- } else {
- System.out.println("Unknown color");
- return -1;
- }
- } else {
- System.out.println("Failed to get color");
- return -1;
- }
- } else {
- System.out.println("Invalid picture");
- return -1;
- }
- }
在主函数中先调用test方法看看能不能成功获取比较图片点的颜色,在下面的方法就不需要一直判断像素为空或者其他异常情况。
GetNumberOfWhite:当图片某列首像素是白色时,调用这个方法得到这一列白色像素的个数,那么蓝色像素个数就是width-白色像素个数即可。采用了二分法加递归。
- private int getNumberOfWhite(int start, int end, int width, Picture picture) {
- if (start <= end) {
- int mid = (start + end) / 2;
- Color color = picture.getColor(width, mid);
- if (color.equals(Color.WHITE)) {
- return getNumberOfWhite(mid + 1, end, width, picture);
- } else if (color.equals(Color.BLUE)) {
- return getNumberOfWhite(start, mid - 1, width, picture);
- }
- }
- return start;
- }
方法GetNumberOfBlue:当图片某列首像素是蓝色时,调用这个方法得到这一列蓝色像素的个数。采用了二分法加递归。
- private int getNumberOfBlue(int start, int end, int width, Picture picture) {
- if (start <= end) {
- int mid = (start + end) / 2;
- Color color = picture.getColor(width, mid);
- if (color.equals(Color.BLUE)) {
- return getNumberOfBlue(mid + 1, end, width, picture);
- } else if (color.equals(Color.WHITE)) {
- return getNumberOfBlue(start, mid - 1, width, picture);
- }
- }
- return start;
- }
验证方法主函数:
- public static void main(String[] args) throws IOException {
- Picture p = new Picture("D:\\develop\\projects\\dataStructure\\homework01\\file\\tomography.png");
- System.out.println(p.test(p, 1));
- System.out.println(p.maxRow(p));
- }
第一行的0:验证能否正确读取判断像素点的颜色,答案是0,表示坐标为(0,0)的像素点是白色。
第二行的1575:表示该图片中最大列长是 1575
用递归和二分法去解决问题,效率很高,有很大收获,卡了很长时间的点是,数组越界了,传入参数时直接传的width而不是width-1.
任务3
6种排序算法都按课堂上讲的去实现,如下:
冒泡排序:
- public class Bubble extends SortAlgorithm {
- /**
- * 冒泡排序法
- */
- @Override
- public void sort(Comparable[] objs) {
- int N = objs.length;
- for (int i = 0; i < N - 1; i++) {
- //每一趟把最大的放在最右边
- for (int j = 0; j < N - i - 1; j++) {
- if (less(objs[j + 1], objs[j])) {
- exchange(objs, j, j + 1);
- }
- }
- }
- }
- public void betterSort(Comparable[] objs) {
- int N = objs.length;
- int lastExchangeIndex = 0; //记录每轮排序过程中最后发生交换操作的位置
- int unOrderedEndIndex = N - 1; //记录需要进行比较(无序)范围的最后位置
- for (int i = 0; i < N - 1; i++) {
- // 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
- boolean flag = true;
- //每一趟把最大的放在最右边
- for (int j = 0; j < unOrderedEndIndex; j++) {
- if (less(objs[j + 1], objs[j])) {
- exchange(objs, j, j + 1);
- flag = false;
- lastExchangeIndex = j;
- }
- }
- unOrderedEndIndex = lastExchangeIndex;
- if (unOrderedEndIndex == 0) {//数组已经有序
- break;
- }
- //这一趟完全没有交换任何元素,数组已经有序
- if (flag) {
- break;
- }
- }
- }
- }
选择排序:
- public class Selection extends SortAlgorithm {
- /**
- * 选择排序法
- */
- @Override
- public void sort(Comparable[] objs) {
- int N = objs.length;
- for (int i = 0; i < N - 1; i++) {
- int minIndex = i;
- // 每一轮循环中找出最小的那个放在头部
- for (int j = i + 1; j < N; j++) {
- if (less(objs[j], objs[minIndex])) {
- minIndex = j;
- }
- }
- exchange(objs, minIndex, i);
- }
- }
- }
插入排序:
public class Insertion extends SortAlgorithm {- /**
- * 插入排序法
- */
- @Override
- public void sort(Comparable[] objs){
- int N = objs.length;
- for(int i = 1; i < N; i++){
- for(int j = i; j > 0 && less(objs[j], objs[j-1]); j--)
- exchange(objs, j, j-1);
- //在for循环中有两个条件,表示,当这两个同时满足时才进行循环体里的内容
- //等价于,循环体中有if判断第二个条件,若是false则break
- }
- }
- }
希尔排序:
- public class Shell extends SortAlgorithm {
- /**
- * 希尔排序
- */
- @Override
- public void sort(Comparable[] objs) {
- //得到h
- int h = 1;
- while (h < objs.length / 3) {
- h = h * 3 + 1;
- }
- //每次进行循环,改变一下h的值
- while (h >= 1) {
- shell(objs, h);
- h /= 3;
- }
- }
- private void shell(Comparable[] objs, int gap) {
- //1.找到待插入元素(索引为i的值)
- for (int i = gap; i < objs.length; i++) {//从第gap个元素开始的后续每一个连续取值
- //2.将待插入元素放到有序数组中
- for (int j = i; j >= gap; j -= gap) {
- //现在待插入元素是num[j]
- //比较num[j]和num[j-gap]的大小
- if (less(objs[j], objs[j - gap])) {
- exchange(objs, j, j - gap);
- } else {
- break;
- }
- }
- }
- }
- }
归并排序:
public class Merge extends SortAlgorithm {- /**
- * 归并排序
- */
- Comparable[] assist;
- @Override
- public void sort(Comparable[] objs) {
- //初始化辅助数组assist
- assist = new Comparable[objs.length];
- sort(objs, 0, objs.length - 1);
- }
- private void sort(Comparable[] objs, int low, int high) {
- if (low >= high) {
- return;
- }
- int mid = (low + high) / 2;
- sort(objs, low, mid);
- sort(objs, mid + 1, high);
- //该合并了
- merge(objs, low, mid, high);
- }
- private void merge(Comparable[] objs, int low, int mid, int high) {
- //定义三个指针
- int p1 = low;
- int p2 = mid + 1;
- int i = low;
- //先把原数组里的排序好放到辅助数组中,再把临时数组中的值赋值回来
- //遍历,移动三个指针,把小的放到assist里面
- while (p1 <= mid || p2 <= high) {
- if (p1 == mid + 1) {//若p1超出了mid,说明p1所在子区域已经被走了一遍
- assist[i] = objs[p2];
- p2++;
- } else if (p2 == high + 1) {//若p2超出了mid,说明p2所在子区域已经被走了一遍
- assist[i] = objs[p1];
- p1++;
- } else if (less(objs[p1], objs[p2])) {//当p1和p2都在对应区域内时,再判断
- assist[i] = objs[p1];
- p1++;
- } else {
- assist[i] = objs[p2];
- p2++;
- }
- i++;
- }
- //拷贝
- for (int index = low; index <= high; index++) {
- objs[index] = assist[index];
- }
- }
- }
快速排序:
public class Quick extends SortAlgorithm {- /**
- * 快速排序
- */
- @Override
- public void sort(Comparable[] objs) {
- sort(objs, 0, objs.length - 1);
- }
- public void sort(Comparable[] objs, int left, int right) {
- if (left < right) {
- int pivotIndex = partition(objs, left, right);
- sort(objs, left, pivotIndex - 1);
- sort(objs, pivotIndex + 1, right);
- }
- }
- private int partition(Comparable[] objs, int left, int right) {
- Comparable pivot = objs[right];
- int i = left - 1;
- for (int j = left; j < right; j++) {
- if (less(objs[j], pivot)) {
- i++;
- exchange(objs, i, j);
- }
- }
- exchange(objs, i + 1, right);
- return i + 1;
- }
- }
测试方法:
- public static void main(String[] args) {
- //冒泡排序
- Double[] numbers_1 = GenerateData.getRandomData(1000);
- SortAlgorithm bubbleAlg = new Bubble();
- bubbleAlg.sort(numbers_1);
- System.out.print("冒泡排序:");
- System.out.println(bubbleAlg.isSorted(numbers_1));
- //选择排序
- Double[] numbers_2 = GenerateData.getRandomData(1000);
- SortAlgorithm selectionAlg = new Selection();
- selectionAlg.sort(numbers_2);
- System.out.print("选择排序:");
- System.out.println(selectionAlg.isSorted(numbers_2));
- //插入排序
- Double[] numbers_3 = GenerateData.getRandomData(1000);
- SortAlgorithm insertionAlg = new Insertion();
- insertionAlg.sort(numbers_3);
- System.out.print("插入排序:");
- System.out.println(insertionAlg.isSorted(numbers_3));
- //希尔排序
- Double[] numbers_4 = GenerateData.getRandomData(1000);
- SortAlgorithm shellAlg = new Shell();
- shellAlg.sort(numbers_4);
- System.out.print("希尔排序:");
- System.out.println(shellAlg.isSorted(numbers_4));
- //归并排序
- Double[] numbers_5 = GenerateData.getRandomData(1000);
- SortAlgorithm mergeAlg = new Merge();
- mergeAlg.sort(numbers_5);
- System.out.print("归并排序:");
- System.out.println(mergeAlg.isSorted(numbers_5));
- //快速排序
- Double[] numbers_6 = GenerateData.getRandomData(1000);
- SortAlgorithm quickAlg = new Quick();
- quickAlg.sort(numbers_6);
- System.out.print("快速排序:");
- System.out.println(quickAlg.isSorted(numbers_6));
- }
对各种排序算法更加熟练,掌握的更好。
附录:
- import javax.imageio.ImageIO;
- import java.awt.*;
- import java.awt.image.BufferedImage;
- import java.io.File;
- import java.io.IOException;
- public class Picture {
- private BufferedImage image;
- private int width;
- private int height;
- public Picture(String filename) throws IOException {
- File file = new File(filename);
- image = ImageIO.read(file);
- width = image.getWidth();
- height = image.getHeight();
- }
- public Picture(int width, int height) {
- this.width = width;
- this.height = height;
- image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- }
- public int getWidth() {
- return width;
- }
- public int getHeight() {
- return height;
- }
- public void setColor(int col, int row, Color c) {
- image.setRGB(col, row, c.getRGB());
- }
- public Color getColor(int col, int row) {
- int rgb = image.getRGB(col, row);
- return new Color(rgb);
- }
- public void save(String filename) throws IOException {
- String suffix = filename.substring(filename.lastIndexOf('.') + 1);
- if (suffix.equalsIgnoreCase("png") || suffix.equalsIgnoreCase("jpg"))
- ImageIO.write(image, suffix, new File(filename));
- }
- public void darker() {
- for (int i = 0; i < width; i++)
- for (int j = 0; j < height; j++) {
- Color c = getColor(i, j);
- setColor(i, j, c.darker());
- }
- }
- public int maxRow(Picture picture) {
- //用1代表蓝色,0代表白色
- int max = 0;
- //查找每一列的蓝色或者白色值
- for (int w = 0; w < picture.getWidth(); w++) {
- //查找蓝色还是查找白色
- int flag = picture.test(picture, w);
- //每一列用二分查找
- //照片最上面可能是蓝色,也可能是白色,如果是蓝色,那么就查蓝色的数量
- if (flag == 0) {//白色
- int number;
- number = getNumberOfWhite(0, picture.getHeight() - 1, w, picture);
- if (number > max) {
- max = picture.getHeight() - number;
- }
- } else if (flag == 1) {//蓝色
- int number;
- number = getNumberOfBlue(0, picture.getHeight() - 1, w, picture);
- if (number > max) {
- max = number;
- }
- }
- }
- return max;
- }
- //仅仅是测试开始的
- private int test(Picture picture, int width) {
- if (picture != null && picture.getWidth() > 0 && picture.getHeight() > 0) {
- Color color = picture.getColor(width, 0);
- if (color != null) {
- if (color.equals(Color.BLUE)) {
- return 1;
- } else if (color.equals(Color.WHITE)) {
- return 0;
- } else {
- System.out.println("Unknown color");
- return -1;
- }
- } else {
- System.out.println("Failed to get color");
- return -1;
- }
- } else {
- System.out.println("Invalid picture");
- return -1;
- }
- }
- public static void main(String[] args) throws IOException {
- Picture p = new Picture("D:\\develop\\projects\\dataStructure\\homework01\\file\\tomography.png");
- System.out.println(p.test(p, 1));
- System.out.println(p.maxRow(p));
- }
- private int getNumberOfWhite(int start, int end, int width, Picture picture) {
- if (start <= end) {
- int mid = (start + end) / 2;
- Color color = picture.getColor(width, mid);
- if (color.equals(Color.WHITE)) {
- return getNumberOfWhite(mid + 1, end, width, picture);
- } else if (color.equals(Color.BLUE)) {
- return getNumberOfWhite(start, mid - 1, width, picture);
- }
- }
- return start;
- }
- private int getNumberOfBlue(int start, int end, int width, Picture picture) {
- if (start <= end) {
- int mid = (start + end) / 2;
- Color color = picture.getColor(width, mid);
- if (color.equals(Color.BLUE)) {
- return getNumberOfBlue(mid + 1, end, width, picture);
- } else if (color.equals(Color.WHITE)) {
- return getNumberOfBlue(start, mid - 1, width, picture);
- }
- }
- return start;
- }
- }
- public class Insertion extends SortAlgorithm {
- /**
- * 插入排序法
- */
- @Override
- public void sort(Comparable[] objs){
- int N = objs.length;
- for(int i = 1; i < N; i++){
- for(int j = i; j > 0 && less(objs[j], objs[j-1]); j--)
- exchange(objs, j, j-1);
- //在for循环中有两个条件,表示,当这两个同时满足时才进行循环体里的内容
- //等价于,循环体中有if判断第二个条件,若是false则break
- }
- }
- }
- public class Bubble extends SortAlgorithm {
- /**
- * 冒泡排序法
- */
- @Override
- public void sort(Comparable[] objs) {
- int N = objs.length;
- for (int i = 0; i < N - 1; i++) {
- //每一趟把最大的放在最右边
- for (int j = 0; j < N - i - 1; j++) {
- if (less(objs[j + 1], objs[j])) {
- exchange(objs, j, j + 1);
- }
- }
- }
- }
- }
- public class Selection extends SortAlgorithm {
- /**
- * 选择排序法
- */
- @Override
- public void sort(Comparable[] objs) {
- int N = objs.length;
- for (int i = 0; i < N - 1; i++) {
- int minIndex = i;
- // 每一轮循环中找出最小的那个放在头部
- for (int j = i + 1; j < N; j++) {
- if (less(objs[j], objs[minIndex])) {
- minIndex = j;
- }
- }
- exchange(objs, minIndex, i);
- }
- }
- }
- public class Shell extends SortAlgorithm {
- /**
- * 希尔排序
- */
- @Override
- public void sort(Comparable[] objs) {
- //得到h
- int h = 1;
- while (h < objs.length / 3) {
- h = h * 3 + 1;
- }
- //每次进行循环,改变一下h的值
- while (h >= 1) {
- shell(objs, h);
- h /= 3;
- }
- }
- private void shell(Comparable[] objs, int gap) {
- //1.找到待插入元素(索引为i的值)
- for (int i = gap; i < objs.length; i++) {//从第gap个元素开始的后续每一个连续取值
- //2.将待插入元素放到有序数组中
- for (int j = i; j >= gap; j -= gap) {
- //现在待插入元素是num[j]
- //比较num[j]和num[j-gap]的大小
- if (less(objs[j], objs[j - gap])) {
- exchange(objs, j, j - gap);
- } else {
- break;
- }
- }
- }
- }
- }
public class Merge extends SortAlgorithm {- /**
- * 归并排序
- */
- Comparable[] assist;
- @Override
- public void sort(Comparable[] objs) {
- //初始化辅助数组assist
- assist = new Comparable[objs.length];
- sort(objs, 0, objs.length - 1);
- }
- private void sort(Comparable[] objs, int low, int high) {
- if (low >= high) {
- return;
- }
- int mid = (low + high) / 2;
- sort(objs, low, mid);
- sort(objs, mid + 1, high);
- //该合并了
- merge(objs, low, mid, high);
- }
- private void merge(Comparable[] objs, int low, int mid, int high) {
- //定义三个指针
- int p1 = low;
- int p2 = mid + 1;
- int i = low;
- //先把原数组里的排序好放到辅助数组中,再把临时数组中的值赋值回来
- //遍历,移动三个指针,把小的放到assist里面
- while (p1 <= mid || p2 <= high) {
- if (p1 == mid + 1) {//若p1超出了mid,说明p1所在子区域已经被走了一遍
- assist[i] = objs[p2];
- p2++;
- } else if (p2 == high + 1) {//若p2超出了mid,说明p2所在子区域已经被走了一遍
- assist[i] = objs[p1];
- p1++;
- } else if (less(objs[p1], objs[p2])) {//当p1和p2都在对应区域内时,再判断
- assist[i] = objs[p1];
- p1++;
- } else {
- assist[i] = objs[p2];
- p2++;
- }
- i++;
- }
- //拷贝
- for (int index = low; index <= high; index++) {
- objs[index] = assist[index];
- }
- }
- }
- public class Quick extends SortAlgorithm {
- /**
- * 快速排序
- */
- @Override
- public void sort(Comparable[] objs) {
- sort(objs, 0, objs.length - 1);
- }
- public void sort(Comparable[] objs, int left, int right) {
- while (left < right) {
- int pivotIndex = partition(objs, left, right);
- if (pivotIndex - left < right - pivotIndex) {
- // 左侧较短
- sort(objs, left, pivotIndex - 1);
- left = pivotIndex + 1;
- } else {
- // 右侧较短
- sort(objs, pivotIndex + 1, right);
- right = pivotIndex - 1;
- }
- }
- }
- private int partition(Comparable[] objs, int left, int right) {
- int i = left, j = right;
- Comparable pivot = objs[left]; // 选择第一个元素作为基准数
- while (i < j) {
- while (i < j && less(pivot, objs[j]))
- j--;
- if (i < j)
- objs[i++] = objs[j];
- while (i < j && !less(pivot, objs[i]))
- i++;
- if (i < j)
- objs[j--] = objs[i];
- }
- objs[i] = pivot;
- return i;
- }
- }
测试方法:
- public static void main(String[] args) {
- //冒泡排序
- Double[] numbers_1 = GenerateData.getRandomData(1000);
- SortAlgorithm bubbleAlg = new Bubble();
- bubbleAlg.sort(numbers_1);
- System.out.print("冒泡排序:");
- System.out.println(bubbleAlg.isSorted(numbers_1));
- //选择排序
- Double[] numbers_2 = GenerateData.getRandomData(1000);
- SortAlgorithm selectionAlg = new Selection();
- selectionAlg.sort(numbers_2);
- System.out.print("选择排序:");
- System.out.println(selectionAlg.isSorted(numbers_2));
- //插入排序
- Double[] numbers_3 = GenerateData.getRandomData(1000);
- SortAlgorithm insertionAlg = new Insertion();
- insertionAlg.sort(numbers_3);
- System.out.print("插入排序:");
- System.out.println(insertionAlg.isSorted(numbers_3));
- //希尔排序
- Double[] numbers_4 = GenerateData.getRandomData(1000);
- SortAlgorithm shellAlg = new Shell();
- shellAlg.sort(numbers_4);
- System.out.print("希尔排序:");
- System.out.println(shellAlg.isSorted(numbers_4));
- //归并排序
- Double[] numbers_5 = GenerateData.getRandomData(1000);
- SortAlgorithm mergeAlg = new Merge();
- mergeAlg.sort(numbers_5);
- System.out.print("归并排序:");
- System.out.println(mergeAlg.isSorted(numbers_5));
- //快速排序
- Double[] numbers_6 = GenerateData.getRandomData(1000);
- SortAlgorithm quickAlg = new Quick();
- quickAlg.sort(numbers_6);
- System.out.print("快速排序:");
- System.out.println(quickAlg.isSorted(numbers_6));
- }