XJTUSE 数据结构与算法第二次作业——任务3和任务4

任务3

  1. 题目

众所周知,栈虽然是一个操作受限的线性表,但是其用途却很广泛,栈的数据结构实现非常简单,因此我们只从应用层面熟悉栈。请完成下面三个子任务。

①递归是一种解决很多复杂问题最简单的思想方法,而任何编程语言对递归程序的支持都是通过栈实现的。请利用课堂上讲解的“Hanoi 塔”问题的非递归转化方法完成对递归快速排序的非递归转化。

②算术混合运算表达式的计算。表达式不仅能处理整数,还需要处理小数。表达式中涉及的运算符包括+、-、*、/、^(指数)。表达式可以包含括号(只包含圆括号)嵌套,因此要处理括号匹配失败的情形。

③当我们在使用很多软件时都有类似“undo”功能,比如 Web 浏览器的回退功能、文本编辑器的撤销编辑功能。这些功能都可以使用 Stack 简单实现,但是在现实中浏览器的回退功能也好,编辑器的撤销功能也好,都有一定的数量限制。因此我们需要的不是一个普通的 Stack 数据结构,而是一个空间有限制的 Stack,虽然空间有限,但这样的 Stack 在入栈时从不会溢出,因为它会采用将最久远的记录丢掉的方式让新元素入栈,也就是说总是按照规定的数量要求保持最近的历史操作。比如栈的空间是 5,当 a\b\c\d\e 入栈之后,如果继续让元素 f 入栈,那么栈中的元素将是 b\c\d\e\f。请设计一个满足上面要求的 LeakyStack 数据结构,要求该数据结构的每一个操作的时间复杂度在最坏情形下都必须满足 O(1)。

  1. 任务1
  1. 算法思路

递归算法通常使用函数调用栈来保存每个递归调用的上下文信息。在非递归实现中,可以使用一个辅助栈来模拟函数调用栈的功能。在每次迭代中,将需要处理的子数组的起始和结束索引压入栈中。

初始化栈并将初始的起始索引和结束索引压入栈,使用Stack<Integer>来表示栈,并初始化一个空栈。然后,我们将初始的起始索引和结束索引压入栈中。

在每次迭代中,从栈中取出起始索引和结束索引,并根据这些索引进行划分。通过调用partition方法来获取枢轴元素的索引,并将其保存为pivotIndex。

检查左子数组和右子数组是否需要进一步划分:在确定了枢轴元素的索引后,需要检查左右子数组是否需要进一步划分。如果左子数组的范围大于1,则将其起始索引和结束索引压入栈中,以便后续处理。同样,如果右子数组的范围大于1,则将其起始索引和结束索引压入栈中。

重复执行步骤3和步骤4,直到栈为空。这样可以保证所有的子数组都得到了正确的划分,并且排序操作会在每个子数组上进行。

  1. 主干代码分析

创建一个栈对象stack,并初始化起始索引和结束索引:

  1.         Stack<Integer> stack = new Stack<>();
  2.         int low = 0;
  3.         int high = array.length - 1;
  4.         stack.push(low);
  5.         stack.push(high);

使用循环迭代处理栈中的元素,在划分操作后,检查左右子数组是否需要进一步划分:

  1.         while (!stack.empty()) {
  2.             high = stack.pop();
  3.             low = stack.pop();
  4.             int pivotIndex = partition(array, low, high);
  5.             if (pivotIndex - 1 > low) {
  6.                 stack.push(low);
  7.                 stack.push(pivotIndex - 1);
  8.             }
  9.             if (pivotIndex + 1 < high) {
  10.                 stack.push(pivotIndex + 1);
  11.                 stack.push(high);
  12.             }
  13.         }

  1. 测试

测试方法:

  1.     public static void main(String[] args) {
  2.         int[] array = {72168534};
  3.         System.out.println("原始数组:");
  4.         printArray(array);
  5.         QuickSortNonRecursive.quickSort(array);
  6.         System.out.println("排序后的数组:");
  7.         printArray(array);
  8.         // 验证排序是否正确
  9.         System.out.println("验证排序是否正确:");
  10.         if (isSorted(array)) {
  11.             System.out.println("数组已正确排序");
  12.         } else {
  13.             System.out.println("数组排序错误");
  14.         }
  15.     }
  16.     private static void printArray(int[] array) {
  17.         for (int num : array) {
  18.             System.out.print(num + " ");
  19.         }
  20.         System.out.println();
  21.     }
  22.     private static boolean isSorted(int[] array) {
  23.         for (int i = 1; i < array.length; i++) {
  24.             if (array[i] < array[i - 1]) {
  25.                 return false;
  26.             }
  27.         }
  28.         return true;
  29.     }

测试结果:

  1. 任务2
  1. 算法思路

有两种思路来处理算术混合运算表达式的计算。可以直接用两个栈进行中缀表达式的计算,也可以先把中缀表达式转化成后缀表达式再放入栈中计算,两者殊途同归,都是利用了栈只能先入先出的规则,优先计算优先程度高的算式部分,其实也可以理解成一种递归。

我采取了先转化后缀表达式再计算的方式。对于输入要求很低,字符间可以用任意个空格分开,或者根本没有空格,能处理表达式中出现括号不匹配、运算符缺少运算操作数等常见的输入错误。

如何处理括号匹配失败,用一个栈就可以实现,遇到左括号'('时,将其入栈;遇到右括号')'时,进行匹配操作即可。遍历结束后,检查栈是否为空:遍历结束后,我们需要检查栈是否为空。如果栈为空(top为-1),说明所有的左括号都被匹配了,括号匹配成功,返回true。否则,栈中还有未匹配的左括号,括号不匹配,返回false。

  1. package homework03;
  2. public class BracketMatching {
  3.     //利用栈完成括号匹配
  4.     static boolean match(char[] exp){
  5.         char stack[] = new char[exp.length];
  6.         int top=-1;
  7.         for (int i=0;i<exp.length;i++){
  8.             if (exp[i]=='('){
  9.                 stack[++top]='(';//遇到'('则入栈
  10.             }
  11.             if (exp[i]==')'){
  12.                 if (top==-1){
  13.                     return false;//栈空说明')'比'('多,不匹配
  14.                 }else {
  15.                     --top;//栈不空则将栈中的一个'('弹出
  16.                 }
  17.             }
  18.         }
  19.         if (top==-1){
  20.             return true;//栈空则说明所有括号都被处理掉
  21.         }
  22.         return false;//否则括号不匹配
  23.     }
  24. }

如何处理小数和多位数,当扫描到数字时,需要另一个指针j往后继续扫描,当后面是数字时说明是多位数,当扫描到小数点时说明是小数,需要用StringBuilder类型把小数或者多位数拼接,转化成String类型入栈s1.切记把i指针前移。

  1.                 if (nifixExp[i] >= '0' && nifixExp[i] <= '9') {
  2.                     int j = i;
  3.                     StringBuilder number = new StringBuilder();
  4.                     while(true){
  5.                         if(j >= nifixExp.length){
  6.                             break;
  7.                         }
  8.                         if(nifixExp[j] >= '0' && nifixExp[j] <= '9'){
  9.                             number.append(nifixExp[j]);
  10.                         } else if (nifixExp[j] == '.') {
  11.                             number.append(nifixExp[j]);
  12.                         } else{
  13.                             break;
  14.                         }
  15.                         j++;
  16.                     }
  17.                     i = j - 1;
  18.                     s2.push(number.toString());
  19.                 } 

  1. 主干代码分析

先把中缀表达式转后缀表达式

1初始化两个栈:运算符栈s1和储存中间结果的栈s2;

2从左至右扫描中缀表达式;

3遇到操作数时,将其压s2;

4遇到运算符时,比较其与s1栈顶运算符的优先级:

如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;

否则,若优先级比栈顶运算符的高,也将运算符压入s1;

否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;

5遇到括号时:

如果是左括号“(”,则直接压入s1

如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃

6重复步骤2至5,直到表达式的最右边

7将s1中剩余的运算符依次弹出并压入s2

8依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

注意点1:栈s1储存Character类型就可以,栈s2必须是Object类型的(需要储存中间结果,有String类型的多位数和小数)。

  1.         LStack<Character> s1 = new LStack<>();//存储运算符的栈
  2.         LStack<Object> s2 = new LStack<>();//存储中间结果的栈

注意点2:切割成char类型后,对每一个char判断符号是否合法。

  1.         for (int i = 0; i < nifix.length(); i++) {
  2.             if (!isValid(nifixExp[i])) {
  3.                 throw new Exception("输入符号有问题");
  4.             }
  5.         }

  1.     private static boolean isValid(char c) {
  2.         return c >= '0' && c <= '9' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^'|| c == ' '|| c == '('|| c == ')'|| c == '.';
  3.     }

注意点3:判断括号是否匹配。遇到左括号'('时,将其入栈;遇到右括号')'时,进行匹配操作即可。遍历结束后,检查栈是否为空:遍历结束后,我们需要检查栈是否为空。如果栈为空(top为-1),说明所有的左括号都被匹配了,括号匹配成功,返回true。否则,栈中还有未匹配的左括号,括号不匹配,返回false。上文算法思路中有代码,这里不再展示。

注意点4:当扫描到数字时,需要另一个指针j往后继续不断扫描,当后面是数字时说明是多位数,当扫描到小数点时说明是小数,需要用StringBuilder类型把小数或者多位数拼接,转化成String类型入栈s1.切记把i指针前移。上文算法思路中有代码,这里不再展示。

注意点5:当扫描到五种运算符时,也需要另一个指针j往后继续不断扫描,当后面是空格时j++,扫描到五种运算符、小数点和右括号时报错,其余情况跳出循环。

  1.                     int j = i + 1;
  2.                     while(true){
  3.                         if(j >= nifixExp.length){
  4.                             break;
  5.                         }
  6.                         if(nifixExp[j] == ' '){//继续循环
  7.                             j++;
  8.                         } else if (nifixExp[j] == '+' ||nifixExp[j] == '-' ||  nifixExp[j] == '*' || nifixExp[j] == '/' ||nifixExp[j] == '^'||nifixExp[j] == '.'||nifixExp[j] == ')') {//报错
  9.                             throw new Exception("运算符缺少运算操作数");
  10.                         } else{
  11.                             break;
  12.                         }
  13.                     }

注意点6:s2pop的逆序才是后缀表达式。

  1.         //依次弹出s2中的元素并输出,
  2.         // 结果的逆序即为中缀表达式对应的后缀表达式
  3.         Object[] temp = new Object[s2.length()];
  4.         for (int i = 0; !s2.isEmpty(); i++) {
  5.             temp[i] = s2.pop();
  6.         }
  7.         StringBuilder stringBuilder = new StringBuilder();
  8.         for (int i = temp.length - 1; i >= 0; i--) {
  9.             stringBuilder.append(temp[i]);
  10.             stringBuilder.append(" ");
  11.         }
  12.         return stringBuilder.toString();

注意点7:运算符优先级比较函数。

  1.     private static boolean comparePriority(char c1, char c2) {//比较运算符优先级
  2.         int priority1 = 0, priority2 = 0;
  3.         switch (c1) {
  4.             case '^' -> priority1 = 3;
  5.             case '*' -> priority1 = 2;
  6.             case '/' -> priority1 = 2;
  7.             case '+' -> priority1 = 1;
  8.             case '-' -> priority1 = 1;
  9.         }
  10.         switch (c2) {
  11.             case '^' -> priority2 = 3;
  12.             case '*' -> priority2 = 2;
  13.             case '/' -> priority2 = 2;
  14.             case '+' -> priority2 = 1;
  15.             case '-' -> priority2 = 1;
  16.         }
  17.         return priority1 - priority2 > 0;//c1优先级较高
  18.     }

中缀表达式转后缀表达式整体代码如下:

  1.     //中缀转后缀
  2.     public static String toPostfix(String nifix) throws Exception {
  3.         LStack<Character> s1 = new LStack<>();//存储运算符的栈
  4.         LStack<Object> s2 = new LStack<>();//存储中间结果的栈
  5.         char[] nifixExp = nifix.toCharArray();//将字符串转化为字符数组
  6.         for (int i = 0; i < nifix.length(); i++) {
  7.             if (!isValid(nifixExp[i])) {
  8.                 throw new Exception("输入符号有问题");
  9.             }
  10.         }
  11.         if (!BracketMatching.match(nifixExp)) {
  12.             throw new Exception("括号不匹配");
  13.         } else {
  14.             for (int i = 0; i < nifixExp.length; i++) {//从左到右扫描
  15.                 //遇到数字则压入s2,此时s2中的数字是String类型的
  16.                 if (nifixExp[i] >= '0' && nifixExp[i] <= '9') {
  17.                     int j = i;
  18.                     StringBuilder number = new StringBuilder();
  19.                     while(true){
  20.                         if(j >= nifixExp.length){
  21.                             break;
  22.                         }
  23.                         if(nifixExp[j] >= '0' && nifixExp[j] <= '9'){
  24.                             number.append(nifixExp[j]);
  25.                         } else if (nifixExp[j] == '.') {
  26.                             number.append(nifixExp[j]);
  27.                         } else{
  28.                             break;
  29.                         }
  30.                         j++;
  31.                     }
  32.                     i = j - 1;
  33.                     s2.push(number.toString());
  34.                 } else if (nifixExp[i] == '(') {//遇到左括号直接压入s1
  35.                     s1.push(nifixExp[i]);
  36.                 } else if (nifixExp[i] == ')') {
  37.                     //遇到右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  38.                     while (!(s1.topValue().equals('('))) {
  39.                         s2.push(s1.pop());
  40.                     }
  41.                     s1.pop();//把左括号也弹出
  42.                 } else if (nifixExp[i] != ' ') {
  43.                     /*
  44.                      * 遇到运算符时,比较其与s1栈顶运算符的优先级:
  45.                      * 1) 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
  46.                      * 2) 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
  47.                      * 3) 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(1)与s1中新的栈顶运算符相比较;
  48.                      */
  49.                     int j = i + 1;
  50.                     while(true){
  51.                         if(j >= nifixExp.length){
  52.                             break;
  53.                         }
  54.                         if(nifixExp[j] == ' '){//继续循环
  55.                             j++;
  56.                         } else if (nifixExp[j] == '+' ||nifixExp[j] == '-' ||  nifixExp[j] == '*' || nifixExp[j] == '/' ||nifixExp[j] == '^'||nifixExp[j] == '.'||nifixExp[j] == ')') {//报错
  57.                             throw new Exception("运算符缺少运算操作数");
  58.                         } else{
  59.                             break;
  60.                         }
  61.                     }
  62.                     while (true) {
  63.                         if (s1.isEmpty() || s1.topValue() == '('
  64.                                 || comparePriority(nifixExp[i], s1.topValue())) {
  65.                             s1.push(nifixExp[i]);
  66.                             break;
  67.                         } else {
  68.                             s2.push(s1.pop());
  69.                         }
  70.                     }
  71.                 }
  72.             }
  73.         }
  74.         //把s1中剩余的运算符一次弹出并压入s2
  75.         while (!s1.isEmpty()) {
  76.             s2.push(s1.pop());
  77.         }
  78.         //依次弹出s2中的元素并输出,
  79.         // 结果的逆序即为中缀表达式对应的后缀表达式
  80.         Object[] temp = new Object[s2.length()];
  81.         for (int i = 0; !s2.isEmpty(); i++) {
  82.             temp[i] = s2.pop();
  83.         }
  84.         StringBuilder stringBuilder = new StringBuilder();
  85.         for (int i = temp.length - 1; i >= 0; i--) {
  86.             stringBuilder.append(temp[i]);
  87.             stringBuilder.append(" ");
  88.         }
  89.         return stringBuilder.toString();
  90.     }
  91.     private static boolean isValid(char c) {
  92.         return c >= '0' && c <= '9' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^'|| c == ' '|| c == '('|| c == ')'|| c == '.';
  93.     }
  94.     private static boolean comparePriority(char c1, char c2) {//比较运算符优先级
  95.         int priority1 = 0, priority2 = 0;
  96.         switch (c1) {
  97.             case '^' -> priority1 = 3;
  98.             case '*' -> priority1 = 2;
  99.             case '/' -> priority1 = 2;
  100.             case '+' -> priority1 = 1;
  101.             case '-' -> priority1 = 1;
  102.         }
  103.         switch (c2) {
  104.             case '^' -> priority2 = 3;
  105.             case '*' -> priority2 = 2;
  106.             case '/' -> priority2 = 2;
  107.             case '+' -> priority2 = 1;
  108.             case '-' -> priority2 = 1;
  109.         }
  110.         return priority1 - priority2 > 0;//c1优先级较高
  111.     }

后缀表达式的计算机求值

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

注意点1:为了区分后缀表达式的多位数,后缀表达式每一个单元word都有一个空格将其隔开。我们需要先根据空格把每一个单元切割开。

  1.          String[] expression = expS.split(" ");

注意点2:对expression中的每一个word,如果是数字,将其转为double类型的数字再入栈。

  1.             if(isNumeric(word)){
  2.                 double number = Double.parseDouble(word);
  3.                 stack.push(number);
  4.             }

  1.     private static boolean isNumeric(String str) {
  2.         try {
  3.             Double.parseDouble(str);
  4.             return true;
  5.         } catch (NumberFormatException e) {
  6.             return false;
  7.         }
  8.     }

后缀表达式求值完整代码:

  1. //利用栈对后缀表达式求值
  2.     static double calculate(String expS) {
  3.         String[] expression = expS.split(" ");
  4.         LStack<Double> stack = new LStack<>();//存储数字的栈
  5.         for (String word : expression) {
  6.             if(isNumeric(word)){
  7.                 double number = Double.parseDouble(word);
  8.                 stack.push(number);
  9.             }else{
  10.                 //弹出栈顶的两个元素
  11.                 double num2 = stack.pop();
  12.                 double num1 = stack.pop();
  13.                 double temp = -1;;
  14.                 switch (word) {
  15.                     case "+" -> temp = num1 + num2;
  16.                     case "-" -> temp = num1 - num2;
  17.                     case "*" -> temp = num1 * num2;
  18.                     case "/" -> temp = num1 / num2;
  19.                     case "^" -> temp = Math.pow(num1, num2);
  20.                 }
  21.                 stack.push(temp);//将中间结果压栈
  22.             }
  23.         }
  24.         return stack.topValue();//返回栈顶元素
  25.     }
  26.     private static boolean isNumeric(String str) {
  27.         try {
  28.             Double.parseDouble(str);
  29.             return true;
  30.         } catch (NumberFormatException e) {
  31.             return false;
  32.         }
  33.     }

  1. 测试

测试方法:

  1.     public static void main(String[] args) throws Exception {
  2.         System.out.print("please input your nifix expression: ");
  3.         Scanner in = new Scanner(System.in);
  4.         String input = in.nextLine();
  5.         String postFix = ArithmeticCalculation.toPostfix(input);
  6.         double result = calculate(postFix);
  7.         System.out.println("the post fix of: " + input + " is " + postFix);
  8.         System.out.println("the result of: " + input + " is " + result);
  9.     }

测试数据:

包括小数,多位数,五种运算符号,左右括号,很多空格或者无空格等中缀表达式。

包括括号不匹配,输入符号有错,连续多个符号等错误。

2+3*4

(7- 4)      *    2 / (1 + 3)

5.42 * (6   + 2.1 ) - 12

3 + 4 * 2 / (1 - 5) ^ 2

3+((4*2)

45 && 2 + (3*4)

7+  + 2*4

测试结果:

任务3

  1. 数据结果设计

为了实现LeakyStack的每个方法都具有O(1)的最坏情况时间复杂度,我们可以使用一个循环数组来实现栈。循环数组是一个固定大小的数组,通过将数组的末尾和开头连接起来,形成一个环形结构。

在LeakyStack中,我们使用一个指针top来表示栈顶元素的位置。该指针随着push和pop操作的进行而不断移动。当执行push操作时,如果栈已满,我们会通过丢弃最早的元素来腾出空间,让新元素入栈。具体来说,当栈已满时,我们将top指针向前移动一位,并将新元素放在这个位置,从而覆盖掉最早的元素。这样,栈的容量始终保持固定,且新元素的入栈操作的时间复杂度为O(1)。

这种设计使得栈的操作时间与栈的容量无关,始终保持恒定的时间复杂度。无论是执行push、pop、topValue还是判断栈是否为空或已满,都只需要对指针进行简单的移动或比较操作,而不需要对整个数组进行移动或复制。

LeakySteak中,有int类型的maxSize表示基于数组实现的栈能够容纳的最大栈元素个数;有int类型的top表示一个指针,指向栈顶位置,初始是-1;T[]类型的listArray定义泛型类型的数组引用;int类型的number表示栈中数据的数量。

  1.     // 数组实现, 一个循环数组,栈顶和栈底位置相差1,像队列
  2.     private int maxSize;             //基于数组实现的栈能够容纳的最大栈元素个数。
  3.     private int top;                 // 指向栈顶位置。
  4.     private T[] listArray;           // 定义泛型类型的数组引用。
  5.     private int number;              // 栈中数据的数量
  6.     public LeakyStack(int maxSize) {
  7.         this.maxSize = maxSize;
  8.         this.top = -1;
  9.         this.listArray = (T[]) new Object[maxSize];
  10.     }

Push入栈:

指针先后移一位,如果越界则说明栈已满,指针指向0处,number--便于与下面的number++代码抵消;number++;it填充到栈顶。

  1.     @Override
  2.     public void push(T it) {
  3.         top++;
  4.         if(top == maxSize){
  5.             top -= maxSize;
  6.             number--;
  7.         }
  8.         number++;
  9.         listArray[top] = it;
  10.     }

Pop出栈:

当栈非空时操作;先储存出栈的数据;将其置为null,top--,判断是否越界并处理;

number--;返回出栈数据。

  1.     @Override
  2.     public T pop() {
  3.         if(!isEmpty()){
  4.             T temp = listArray[top];
  5.             listArray[top] = null;
  6.             top--;
  7.             if(top == -1){
  8.                 top += maxSize;
  9.             }
  10.             number--;
  11.             return temp;
  12.         }
  13.         return null;
  14.     }

其他操作相对简单不在此处进行分析。

  1.     @Override
  2.     public void clear() {
  3.         top = -1;
  4.         number =  0;
  5.     }
  6.     @Override
  7.     public T topValue() {
  8.         return listArray[top];
  9.     }
  10.     @Override
  11.     public int length() {
  12.         return number;
  13.     }
  14.     @Override
  15.     public boolean isEmpty() {
  16.         return number == 0;
  17.     }
  18.     @Override
  19.     public boolean isFull() {
  20.         return number == maxSize;
  21.     }

  1. 时间复杂度分析

clear(): 时间复杂度为O(1)。该方法只是简单地将栈顶指针和元素数量重置为0,没有遍历操作。

push(T it): 时间复杂度为O(1)。该方法只是将新元素入栈,并更新栈顶指针和元素数量。

pop(): 时间复杂度为O(1)。该方法只是将栈顶元素弹出,并更新栈顶指针和元素数量。

topValue(): 时间复杂度为O(1)。该方法只是返回栈顶元素,并不改变栈的结构。

length(): 时间复杂度为O(1)。该方法只是返回当前栈中元素的数量,由于维护了一个变量来记录元素数量,所以可以直接返回。

isEmpty(): 时间复杂度为O(1)。该方法只是判断栈是否为空,通过检查元素数量是否为0来确定。

isFull(): 时间复杂度为O(1)。该方法只是判断栈是否已满,通过检查元素数量是否达到最大容量来确定。

LeakyStack的每个方法都具有O(1)的最坏情况时间复杂度。这是因为使用了一个循环数组来实现栈,并且在入栈时通过丢弃最早的元素来保持固定的容量,而无需对整个数组进行移动或复制操作。这种设计使得栈的操作时间与栈的容量无关,始终保持恒定的时间复杂度。

  1. 测试
  1.     public static void main(String[] args) {
  2.         LeakyStack<String> stack = new LeakyStack<>(5);
  3.         stack.push("a");
  4.         stack.push("b");
  5.         stack.push("c");
  6.         stack.push("d");
  7.         stack.push("e");
  8.         System.out.println("栈长度:" + stack.length());  // 输出:栈长度:5
  9.         System.out.println("栈顶元素:" + stack.topValue());  // 输出:栈顶元素:e
  10.         stack.push("f");
  11.         System.out.println("栈长度:" + stack.length());  // 输出:栈长度:5
  12.         System.out.println("栈顶元素:" + stack.topValue());  // 输出:栈顶元素:f
  13.         String poppedValue = stack.pop();
  14.         System.out.println("弹出的元素:" + poppedValue);  // 输出:弹出的元素:f
  15.         System.out.println("栈长度:" + stack.length());  // 输出:栈长度:4
  16.         System.out.println("栈顶元素:" + stack.topValue());  // 输出:栈顶元素:e
  17.         stack.clear();
  18.         System.out.println("栈长度:" + stack.length());  // 输出:栈长度:0
  19.         System.out.println("栈是否为空:" + stack.isEmpty());  // 输出:栈是否为空:true
  20.     }

结果:

  1. 总结和收获

任务1学到了递归问题如何转化成非递归问题。

任务2复习了字符字符串相关操作,深入理解了栈的应用,更加理解递归,也学到了中缀表达式和后缀表达式。

任务3自己写了一个栈的一个具体应用的数据机构,这说明针对具体的问题,要用更加具体的切合实际的数据结构,要定制数据结构。

任务4

  1. 题目

使用自定义的队列数据结构实现对某一个数据序列的排序(采用基数排序),其中对待排序数据有如下的要求:

①当数据序列是整数类型的数据的时候,数据序列中每个数据的位数不要求等宽,比如: 1、21、12、322、44、123、2312、765、56

②当数据序列是字符串类型的数据的时候,数据序列中每个字符串都是等宽的,比如:

"abc","bde","fad","abd","bef","fdd","abe"

注:radixsort1.txt 和 radixsort2.txt 是为上面两个数据序列提供的测试数据。

  1. 数据设计

需要做一个队列Queue,链式队列最容易实现,列表只需要单链表就可以,也不需要头尾哨兵节点,有两个指针分别指向队头和队尾,有一个int类型的size储存列表的节点数量。当列表为空的时候两个指针指向null。

  1. public class MyQueue<Timplements Queue<T>{
  2.     private static class Node<T> {
  3.         T value;
  4.         Node<T> next;
  5.         public Node(T value, Node<T> next) {
  6.             this.value = value;
  7.             this.next = next;
  8.         }
  9.     }
  10.     private Node<T> front;
  11.     private Node<T> rear;
  12.     private int size;
  13.     public MyQueue() {
  14.         this.front = null;
  15.         this.rear = null;
  16.         this.size = 0;
  17.     }
  18. }

列表实现起来非常简单,队尾添加元素的add方法,当列表为空的时候将头尾指针指向新加入的节点,当列表非空的时候将尾指针指向新加的节点即可。

  1.     @Override
  2.     public boolean add(T data) {
  3.         if (isEmpty()) {
  4.             Node<T> added = new Node<>(data, null);
  5.             front = added;
  6.             rear = added;
  7.             return true;
  8.         } else {
  9.             Node<T> added = new Node<>(data, null);
  10.             rear.next = added;
  11.             rear = added;
  12.             return true;
  13.         }
  14.     }

队头删除元素的poll方法,当列表非空的时候将头指针向前指一位,移除后,检查下列表是否为空,如果是将尾指针也置空。

  1.     @Override
  2.     public T poll() {
  3.         if (isEmpty()) {
  4.             return null;
  5.         }
  6.         Node<T> temp = front;
  7.         front = front.next;
  8.         if (front == null) {
  9.             // 如果队列为空,更新rear为null
  10.             rear = null;
  11.         }
  12.         return temp.value;
  13.     }

  1. 算法设计和主干代码分析

对于数字和等长字符串,采用最低位优先法排序最快速。LSD基数排序算法从最低有效位开始,通过将元素按照当前位的值分配到不同的队列中,然后按照队列的顺序将元素收集回原始数组。然后,它继续处理下一位,重复这个过程,直到处理完最高有效位为止。通过这样的迭代,元素将按照每个位的顺序进行排序,最终实现整体的排序。

LSD(Least Significant Digit)基数排序算法的时间复杂度取决于输入数据的位数和数据范围。假设输入数据的长度为n,位数为d,数据范围为k。

时间复杂度:O(d * (n + k))

1.外层循环迭代d次,每次迭代处理一个位数。

2.在每个位数上,需要对n个元素进行分配到k个桶的操作,这需要线性时间,即O(n)。

3.需要从k个桶中收集元素放回原始数组,也需要线性时间,即O(n)。

4.每个位数上的操作总共需要O(n + k)的时间。

对整数排序的LSD算法:

1.用一个List<MyQueue<Integer>> queues创建10个MyQueue队列,用于存储待排序的整数,每个队列代表一个数字(0到9)。

  1.         //十个队列,queues.get(2);表示拿出第三个队列
  2.         List<MyQueue<Integer>> queues = new ArrayList<>();
  3.         //最开始建10个队列
  4.         for (int i = 0; i < 10; i++) {
  5.             MyQueue<Integer> queue = new MyQueue<>();
  6.             queues.add(queue);
  7.         }

2.确定数组中最大整数的位数,即digits将整个数组寻找最大的数,再得到最高位数。

  1.     private static int getNumDigits(int[] num) {//获得最大的数的位数
  2.         int max = num[0];//最大的数
  3.         int digits = 0;//位数
  4.         for (int i = 0; i < num.length; i++) {
  5.             if (num[i] > max) max = num[i];
  6.         }
  7.         while (max / 10 != 0) {
  8.             digits++;
  9.             max = max / 10;
  10.         }
  11.         if (max % 10 != 0) {
  12.             digits++;
  13.         }
  14.         return digits;
  15.     }

3.初始化mode为1,用一个while循环,从最低位开始排序,直到最高位。

  1.         int mode = 1;
  2.         while (digits != 0) {
  3.             //入队
  4.             //出队
  5.             digits--;//位上移
  6.             mode = mode * 10;
  7.         }

4.将数组中的元素根据当前位的值入队到相应的队列中。通过 (value / mode) % 10 可以获取当前位的值

  1.             //入队
  2.             for (int value : num) {
  3.                 queues.get((value / mode) % 10).add(value);
  4.             }

5.从0到9遍历队列,依次将队列中的元素出队并放回原始数组中。

  1.             //出队,k表示数组中的
  2.             int k = 0;
  3.             //j表示第几个队列
  4.             for(int j = 0; j < 10; j++){
  5.                 while(!queues.get(j).isEmpty()){
  6.                     num[k] = queues.get(j).poll();
  7.                     k++;
  8.                 }
  9.             }

对字符串排序的LSD算法:

1.用一个List<MyQueue<String>> queues创建52个MyQueue队列,用于存储待排序的大小写字母,每个队列代表一个字母。

  1.         //52个队列,queues.get(2);表示拿出第三个队列
  2.         List<MyQueue<String>> queues = new ArrayList<>();
  3.         //最开始建52个队列
  4.         for (int i = 0; i < 52; i++) {
  5.             MyQueue<String> queue = new MyQueue<>();
  6.             queues.add(queue);
  7.         }

2.确定数组中最位数,即digits因为本题目处理相同位数的字母,只需要得到str[0]的长度即可。

3.初始化mode为1,用一个while循环,从最低位开始排序,直到最高位。跟整数LSD一致,不再给出代码。

4.根据当前判断的位数将数组中的元素的值入队到相应的队列中。

  1.             //入队
  2.             for (String letter : str) {
  3.                 queues.get(getSize(getCharByDigits(digits,letter))).add(letter);
  4.             }

  1.     //根据几位数返回char
  2.     private static char getCharByDigits(int digit, String string){
  3.         char[] charArray = string.toCharArray();
  4.         return charArray[digit - 1];
  5.     }

  1.     private static int getSize(char letter){
  2.         if('a' <= letter && letter <= 'z'){
  3.             //a返回的是0
  4.             return letter - 97;
  5.         } else if ('A' <= letter && letter <= 'Z') {
  6.             //A返回的是26
  7.             return letter - 65 + 26;
  8.         }
  9.         return -1;
  10.     }

5.遍历队列,依次将队列中的元素出队并放回原始数组中。代码与整数LSD一致,在这里不再展示。

  1. 测试代码

我希望看到排序前后的数据,希望得到运行时间,希望将数据50个一组存入新的output文件,当然必不可少的是读取文件中的数据。

读取文件中的数据,两个方法分别是readNumbersFromFilereadStringsFromFile

  1.     private static int[] readNumbersFromFile(String filePath) {
  2.         int[] numbers = null;
  3.         try {
  4.             File file = new File(filePath);
  5.             Scanner scanner = new Scanner(file);
  6.             //计算文件中有多少个数
  7.             int count = 0;
  8.             while (scanner.hasNext()) {
  9.                 if (scanner.hasNextInt()) {
  10.                     scanner.nextInt();
  11.                     count++;
  12.                 } else {
  13.                     scanner.next();
  14.                 }
  15.             }
  16.             scanner.close();
  17.             //把数字填入数组
  18.             numbers = new int[count];
  19.             scanner = new Scanner(file);
  20.             int index = 0;
  21.             while (scanner.hasNext()) {
  22.                 if (scanner.hasNextInt()) {
  23.                     numbers[index] = scanner.nextInt();
  24.                     index++;
  25.                 } else {
  26.                     scanner.next();
  27.                 }
  28.             }
  29.             scanner.close();
  30.         } catch (FileNotFoundException e) {
  31.             e.printStackTrace();
  32.         }
  33.         return numbers;
  34.     }
  35.     private static String[] readStringsFromFile(String filePath) {
  36.         String[] strings = null;
  37.         try {
  38.             File file = new File(filePath);
  39.             Scanner scanner = new Scanner(file);
  40.             // 计算文件中有多少个字符串
  41.             int count = 0;
  42.             while (scanner.hasNext()) {
  43.                 if (scanner.hasNext()) {
  44.                     scanner.next();
  45.                     count++;
  46.                 } else {
  47.                     scanner.next();
  48.                 }
  49.             }
  50.             scanner.close();
  51.             // 把字符串填入数组
  52.             strings = new String[count];
  53.             scanner = new Scanner(file);
  54.             int index = 0;
  55.             while (scanner.hasNext()) {
  56.                 if (scanner.hasNext()) {
  57.                     strings[index] = scanner.next();
  58.                     index++;
  59.                 } else {
  60.                     scanner.next();
  61.                 }
  62.             }
  63.             scanner.close();
  64.         } catch (FileNotFoundException e) {
  65.             e.printStackTrace();
  66.         }
  67.         return strings;
  68.     }

将排序好的数据50个一行存入新的output文件。

  1.     private static void writeNumbersToFile(int[] numbers, String filePath) {
  2.         try {
  3.             PrintWriter writer = new PrintWriter(filePath);
  4.             int count = 0;
  5.             for (int number : numbers) {
  6.                 writer.print(number + " ");
  7.                 count++;
  8.                 if (count % 50 == 0) {
  9.                     writer.println();
  10.                 }
  11.             }
  12.             writer.close();
  13.         } catch (FileNotFoundException e) {
  14.             e.printStackTrace();
  15.         }
  16.     }
  17.     private static void writeStringsToFile(String[] strings, String filePath) {
  18.         try {
  19.             PrintWriter writer = new PrintWriter(filePath);
  20.             int count = 0;
  21.             for (String str : strings) {
  22.                 writer.print(str + " ");
  23.                 count++;
  24.                 if (count % 50 == 0) {
  25.                     writer.println();
  26.                 }
  27.             }
  28.             writer.close();
  29.         } catch (FileNotFoundException e) {
  30.             e.printStackTrace();
  31.         }
  32.     }
  1. 运行结果展示
     

  1. 总结和收获

学习了基数排序LSD算法,也进一步学习了文件的读取,写出,整体收获很大。

附录:

  1. 任务3
  1. QuickSortNonRecursive:
  1. package homework03;
  2. import java.util.Stack;
  3. public class QuickSortNonRecursive {
  4.     public static void quickSort(int[] array) {
  5.         Stack<Integer> stack = new Stack<>();
  6.         int low = 0;
  7.         int high = array.length - 1;
  8.         stack.push(low);
  9.         stack.push(high);
  10.         while (!stack.empty()) {
  11.             high = stack.pop();
  12.             low = stack.pop();
  13.             int pivotIndex = partition(array, low, high);
  14.             if (pivotIndex - 1 > low) {
  15.                 stack.push(low);
  16.                 stack.push(pivotIndex - 1);
  17.             }
  18.             if (pivotIndex + 1 < high) {
  19.                 stack.push(pivotIndex + 1);
  20.                 stack.push(high);
  21.             }
  22.         }
  23.     }
  24.     private static int partition(int[] array, int low, int high) {
  25.         int pivot = array[high];
  26.         int i = low - 1;
  27.         for (int j = low; j < high; j++) {
  28.             if (array[j] < pivot) {
  29.                 i++;
  30.                 swap(array, i, j);
  31.             }
  32.         }
  33.         swap(array, i + 1, high);
  34.         return i + 1;
  35.     }
  36.     private static void swap(int[] array, int i, int j) {
  37.         int temp = array[i];
  38.         array[i] = array[j];
  39.         array[j] = temp;
  40.     }
  41.     public static void main(String[] args) {
  42.         int[] array = {72168534};
  43.         System.out.println("原始数组:");
  44.         printArray(array);
  45.         QuickSortNonRecursive.quickSort(array);
  46.         System.out.println("排序后的数组:");
  47.         printArray(array);
  48.         // 验证排序是否正确
  49.         System.out.println("验证排序是否正确:");
  50.         if (isSorted(array)) {
  51.             System.out.println("数组已正确排序");
  52.         } else {
  53.             System.out.println("数组排序错误");
  54.         }
  55.     }
  56.     private static void printArray(int[] array) {
  57.         for (int num : array) {
  58.             System.out.print(num + " ");
  59.         }
  60.         System.out.println();
  61.     }
  62.     private static boolean isSorted(int[] array) {
  63.         for (int i = 1; i < array.length; i++) {
  64.             if (array[i] < array[i - 1]) {
  65.                 return false;
  66.             }
  67.         }
  68.         return true;
  69.     }
  70. }

  1. 老师提供的Stack接口不再展示:
  2. 老师提供的LStack不在展示:
  3. BracketMatching:
  1. package homework03;
  2. public class BracketMatching {
  3.     //利用栈完成括号匹配
  4.     static boolean match(char[] exp){
  5.         char stack[] = new char[exp.length];
  6.         int top=-1;
  7.         for (int i=0;i<exp.length;i++){
  8.             if (exp[i]=='('){
  9.                 stack[++top]='(';//遇到'('则入栈
  10.             }
  11.             if (exp[i]==')'){
  12.                 if (top==-1){
  13.                     return false;//栈空说明')'比'('多,不匹配
  14.                 }else {
  15.                     --top;//栈不空则将栈中的一个'('弹出
  16.                 }
  17.             }
  18.         }
  19.         if (top==-1){
  20.             return true;//栈空则说明所有括号都被处理掉
  21.         }
  22.         return false;//否则括号不匹配
  23.     }
  24. }

  1. ArithmeticCalculation:
  1. package homework03;
  2. import java.util.Scanner;
  3. /**
  4.  * post fix:后缀表达式。
  5.  * nifix:中缀表达式。
  6.  */
  7. public class ArithmeticCalculation {
  8.     //中缀转后缀
  9.     public static String toPostfix(String nifix) throws Exception {
  10.         LStack<Character> s1 = new LStack<>();//存储运算符的栈
  11.         LStack<Object> s2 = new LStack<>();//存储中间结果的栈
  12.         char[] nifixExp = nifix.toCharArray();//将字符串转化为字符数组
  13.         for (int i = 0; i < nifix.length(); i++) {
  14.             if (!isValid(nifixExp[i])) {
  15.                 throw new Exception("输入符号有问题");
  16.             }
  17.         }
  18.         if (!BracketMatching.match(nifixExp)) {
  19.             throw new Exception("括号不匹配");
  20.         } else {
  21.             for (int i = 0; i < nifixExp.length; i++) {//从左到右扫描
  22.                 //遇到数字则压入s2,此时s2中的数字是String类型的
  23.                 if (nifixExp[i] >= '0' && nifixExp[i] <= '9') {
  24.                     int j = i;
  25.                     StringBuilder number = new StringBuilder();
  26.                     while(true){
  27.                         if(j >= nifixExp.length){
  28.                             break;
  29.                         }
  30.                         if(nifixExp[j] >= '0' && nifixExp[j] <= '9'){
  31.                             number.append(nifixExp[j]);
  32.                         } else if (nifixExp[j] == '.') {
  33.                             number.append(nifixExp[j]);
  34.                         } else{
  35.                             break;
  36.                         }
  37.                         j++;
  38.                     }
  39.                     i = j - 1;
  40.                     s2.push(number.toString());
  41.                 } else if (nifixExp[i] == '(') {//遇到左括号直接压入s1
  42.                     s1.push(nifixExp[i]);
  43.                 } else if (nifixExp[i] == ')') {
  44.                     //遇到右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  45.                     while (!(s1.topValue().equals('('))) {
  46.                         s2.push(s1.pop());
  47.                     }
  48.                     s1.pop();//把左括号也弹出
  49.                 } else if (nifixExp[i] != ' ') {
  50.                     /*
  51.                      * 遇到运算符时,比较其与s1栈顶运算符的优先级:
  52.                      * 1) 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
  53.                      * 2) 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
  54.                      * 3) 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(1)与s1中新的栈顶运算符相比较;
  55.                      */
  56.                     int j = i + 1;
  57.                     while(true){
  58.                         if(j >= nifixExp.length){
  59.                             break;
  60.                         }
  61.                         if(nifixExp[j] == ' '){//继续循环
  62.                             j++;
  63.                         } else if (nifixExp[j] == '+' ||nifixExp[j] == '-' ||  nifixExp[j] == '*' || nifixExp[j] == '/' ||nifixExp[j] == '^'||nifixExp[j] == '.'||nifixExp[j] == ')') {//报错
  64.                             throw new Exception("运算符缺少运算操作数");
  65.                         } else{
  66.                             break;
  67.                         }
  68.                     }
  69.                     while (true) {
  70.                         if (s1.isEmpty() || s1.topValue() == '('
  71.                                 || comparePriority(nifixExp[i], s1.topValue())) {
  72.                             s1.push(nifixExp[i]);
  73.                             break;
  74.                         } else {
  75.                             s2.push(s1.pop());
  76.                         }
  77.                     }
  78.                 }
  79.             }
  80.         }
  81.         //把s1中剩余的运算符一次弹出并压入s2
  82.         while (!s1.isEmpty()) {
  83.             s2.push(s1.pop());
  84.         }
  85.         //依次弹出s2中的元素并输出,
  86.         // 结果的逆序即为中缀表达式对应的后缀表达式
  87.         Object[] temp = new Object[s2.length()];
  88.         for (int i = 0; !s2.isEmpty(); i++) {
  89.             temp[i] = s2.pop();
  90.         }
  91.         StringBuilder stringBuilder = new StringBuilder();
  92.         for (int i = temp.length - 1; i >= 0; i--) {
  93.             stringBuilder.append(temp[i]);
  94.             stringBuilder.append(" ");
  95.         }
  96.         return stringBuilder.toString();
  97.     }
  98.     private static boolean isValid(char c) {
  99.         return c >= '0' && c <= '9' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^'|| c == ' '|| c == '('|| c == ')'|| c == '.';
  100.     }
  101.     private static boolean comparePriority(char c1, char c2) {//比较运算符优先级
  102.         int priority1 = 0, priority2 = 0;
  103.         switch (c1) {
  104.             case '^' -> priority1 = 3;
  105.             case '*' -> priority1 = 2;
  106.             case '/' -> priority1 = 2;
  107.             case '+' -> priority1 = 1;
  108.             case '-' -> priority1 = 1;
  109.         }
  110.         switch (c2) {
  111.             case '^' -> priority2 = 3;
  112.             case '*' -> priority2 = 2;
  113.             case '/' -> priority2 = 2;
  114.             case '+' -> priority2 = 1;
  115.             case '-' -> priority2 = 1;
  116.         }
  117.         return priority1 - priority2 > 0;//c1优先级较高
  118.     }
  119.     //利用栈对后缀表达式求值
  120.     static double calculate(String expS) {
  121.         String[] expression = expS.split(" ");
  122.         LStack<Double> stack = new LStack<>();//存储数字的栈
  123.         for (String word : expression) {
  124.             if(isNumeric(word)){
  125.                 double number = Double.parseDouble(word);
  126.                 stack.push(number);
  127.             }else{
  128.                 //弹出栈顶的两个元素
  129.                 double num2 = stack.pop();
  130.                 double num1 = stack.pop();
  131.                 double temp = -1;;
  132.                 switch (word) {
  133.                     case "+":
  134.                         temp = num1 + num2;
  135.                         break;
  136.                     case "-":
  137.                         temp = num1 - num2;
  138.                         break;
  139.                     case "*":
  140.                         temp = num1 * num2;
  141.                         break;
  142.                     case "/":
  143.                         temp = num1 / num2;
  144.                         break;
  145.                     case "^":
  146.                         temp = Math.pow(num1,num2);
  147.                         break;
  148.                 }
  149.                 stack.push(temp);//将中间结果压栈
  150.             }
  151.         }
  152.         return stack.topValue();//返回栈顶元素
  153.     }
  154.     private static boolean isNumeric(String str) {
  155.         try {
  156.             Double.parseDouble(str);
  157.             return true;
  158.         } catch (NumberFormatException e) {
  159.             return false;
  160.         }
  161.     }
  162.     public static void main(String[] args) throws Exception {
  163.         System.out.print("please input your nifix expression: ");
  164.         Scanner in = new Scanner(System.in);
  165.         String input = in.nextLine();
  166.         String postFix = ArithmeticCalculation.toPostfix(input);
  167.         double result = calculate(postFix);
  168.         System.out.println("the post fix of: " + input + " is " + postFix);
  169.         System.out.println("the result of: " + input + " is " + result);
  170.     }
  171. }

  1. LeakyStack:
  1. package homework03;
  2. /**
  3.  * 当我们在使用很多软件时都有类似“undo”功能,比如 Web 浏览器的回退功能、文本编辑器
  4.  * 的撤销编辑功能。这些功能都可以使用 Stack 简单实现,但是在现实中浏览器的回退功能也好,
  5.  * 编辑器的撤销功能也好,都有一定的数量限制。因此我们需要的不是一个普通的 Stack 数据结构,
  6.  * 而是一个空间有限制的 Stack,虽然空间有限,但这样的 Stack 在入栈时从不会溢出,因为它会
  7.  * 采用将最久远的记录丢掉的方式让新元素入栈,也就是说总是按照规定的数量要求保持最近的历
  8.  * 史操作。比如栈的空间是 5,当 a\b\c\d\e 入栈之后,如果继续让元素 f 入栈,那么栈中的元素
  9.  * 将是 b\c\d\e\f。请设计一个满足上面要求的 LeakyStack 数据结构,要求该数据结构的每一个操
  10.  * 作的时间复杂度在最坏情形下都必须满足 O(1)。
  11.  */
  12. public class LeakyStack<Timplements Stack<T> {
  13.     // 数组实现, 一个循环数组,栈顶和栈底位置相差1,像队列
  14.     private int maxSize;             //基于数组实现的栈能够容纳的最大栈元素个数。
  15.     private int top;                 // 指向栈顶位置。
  16.     private T[] listArray;           // 定义泛型类型的数组引用。
  17.     private int number;              // 栈中数据的数量
  18.     public LeakyStack(int maxSize) {
  19.         this.maxSize = maxSize;
  20.         this.top = -1;
  21.         this.listArray = (T[]) new Object[maxSize];
  22.     }
  23.     @Override
  24.     public void clear() {
  25.         top = -1;
  26.         number =  0;
  27.     }
  28.     @Override
  29.     public void push(T it) {
  30.         top++;
  31.         if(top == maxSize){
  32.             top -= maxSize;
  33.             number--;
  34.         }
  35.         number++;
  36.         listArray[top] = it;
  37.     }
  38.     @Override
  39.     public T pop() {
  40.         if(!isEmpty()){
  41.             T temp = listArray[top];
  42.             listArray[top] = null;
  43.             top--;
  44.             if(top == -1){
  45.                 top += maxSize;
  46.             }
  47.             number--;
  48.             return temp;
  49.         }
  50.         return null;
  51.     }
  52.     @Override
  53.     public T topValue() {
  54.         return listArray[top];
  55.     }
  56.     @Override
  57.     public int length() {
  58.         return number;
  59.     }
  60.     @Override
  61.     public boolean isEmpty() {
  62.         return number == 0;
  63.     }
  64.     @Override
  65.     public boolean isFull() {
  66.         return number == maxSize;
  67.     }
  68.     public static void main(String[] args) {
  69.         LeakyStack<String> stack = new LeakyStack<>(5);
  70.         stack.push("a");
  71.         stack.push("b");
  72.         stack.push("c");
  73.         stack.push("d");
  74.         stack.push("e");
  75.         System.out.println("栈长度:" + stack.length());  // 输出:栈长度:5
  76.         System.out.println("栈顶元素:" + stack.topValue());  // 输出:栈顶元素:e
  77.         stack.push("f");
  78.         System.out.println("栈长度:" + stack.length());  // 输出:栈长度:5
  79.         System.out.println("栈顶元素:" + stack.topValue());  // 输出:栈顶元素:f
  80.         String poppedValue = stack.pop();
  81.         System.out.println("弹出的元素:" + poppedValue);  // 输出:弹出的元素:f
  82.         System.out.println("栈长度:" + stack.length());  // 输出:栈长度:4
  83.         System.out.println("栈顶元素:" + stack.topValue());  // 输出:栈顶元素:e
  84.         stack.clear();
  85.         System.out.println("栈长度:" + stack.length());  // 输出:栈长度:0
  86.         System.out.println("栈是否为空:" + stack.isEmpty());  // 输出:栈是否为空:true
  87.     }
  88. }

  1. 任务4
  1. MyQueue:
  1. package Homework04;
  2. /**
  3.  * 链式队列:
  4.  * 单链表,带头尾指针,没有头尾哨兵
  5.  */
  6. public class MyQueue<Timplements Queue<T>{
  7.     private static class Node<T> {
  8.         T value;
  9.         Node<T> next;
  10.         public Node(T value, Node<T> next) {
  11.             this.value = value;
  12.             this.next = next;
  13.         }
  14.     }
  15.     private Node<T> front;
  16.     private Node<T> rear;
  17.     private int size;
  18.     public MyQueue() {
  19.         this.front = null;
  20.         this.rear = null;
  21.         this.size = 0;
  22.     }
  23.     @Override
  24.     public int size() {
  25.         return size;
  26.     }
  27.     @Override
  28.     public boolean isEmpty() {
  29.         return front==null&&rear==null;
  30.     }
  31.     @Override
  32.     public boolean add(T data) {
  33.         if (isEmpty()) {
  34.             Node<T> added = new Node<>(data, null);
  35.             front = added;
  36.             rear = added;
  37.             return true;
  38.         } else {
  39.             Node<T> added = new Node<>(data, null);
  40.             rear.next = added;
  41.             rear = added;
  42.             return true;
  43.         }
  44.     }
  45.     @Override
  46.     public T peek() {
  47.         if(isEmpty()){
  48.             return null;
  49.         }
  50.         return front.value;
  51.     }
  52.     @Override
  53.     public T poll() {
  54.         if (isEmpty()) {
  55.             return null;
  56.         }
  57.         Node<T> temp = front;
  58.         front = front.next;
  59.         if (front == null) {
  60.             // 如果队列为空,更新rear为null
  61.             rear = null;
  62.         }
  63.         return temp.value;
  64.     }
  65.     @Override
  66.     public void clearQueue() {
  67.         front = null;
  68.         rear = null;
  69.     }
  70. }

  1. RadixSort:
  1. package Homework04;
  2. import java.io.*;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import java.util.Scanner;
  6. public class RadixSort {
  7.     //用最低位排序法最简单
  8.     public static void LSD(int[] num) {
  9.         //十个队列,queues.get(2);表示拿出第三个队列
  10.         List<MyQueue<Integer>> queues = new ArrayList<>();
  11.         //最开始建10个队列
  12.         for (int i = 0; i < 10; i++) {
  13.             MyQueue<Integer> queue = new MyQueue<>();
  14.             queues.add(queue);
  15.         }
  16.         int digits = getNumDigits(num);
  17.         int mode = 1;
  18.         while (digits != 0) {
  19.             //入队
  20.             for (int value : num) {
  21.                 queues.get((value / mode) % 10).add(value);
  22.             }
  23.             //出队,k表示数组中的
  24.             int k = 0;
  25.             //j表示第几个队列
  26.             for(int j = 0; j < 10; j++){
  27.                 while(!queues.get(j).isEmpty()){
  28.                     num[k] = queues.get(j).poll();
  29.                     k++;
  30.                 }
  31.             }
  32.             digits--;//位上移
  33.             mode = mode * 10;
  34.         }
  35.     }
  36.     public static void LSD(String[] str) {
  37.         //52个队列,queues.get(2);表示拿出第三个队列
  38.         List<MyQueue<String>> queues = new ArrayList<>();
  39.         //最开始建52个队列
  40.         for (int i = 0; i < 52; i++) {
  41.             MyQueue<String> queue = new MyQueue<>();
  42.             queues.add(queue);
  43.         }
  44.         int digits = str[0].length();//等长字符的长度
  45.         //位数
  46.         while (digits != 0) {
  47.             //入队
  48.             for (String letter : str) {
  49.                 queues.get(getSize(getCharByDigits(digits,letter))).add(letter);
  50.             }
  51.             //出队,k表示数组中的
  52.             int k = 0;
  53.             //j表示第几个队列
  54.             for(int j = 0; j < 52; j++){
  55.                 while(!queues.get(j).isEmpty()){
  56.                     str[k] = queues.get(j).poll();
  57.                     k++;
  58.                 }
  59.             }
  60.             digits--;//位上移
  61.         }
  62.     }
  63.     private static int getNumDigits(int[] num) {//获得最大的数的位数
  64.         int max = num[0];//最大的数
  65.         int digits = 0;//位数
  66.         for (int i = 0; i < num.length; i++) {
  67.             if (num[i] > max) max = num[i];
  68.         }
  69.         while (max / 10 != 0) {
  70.             digits++;
  71.             max = max / 10;
  72.         }
  73.         if (max % 10 != 0) {
  74.             digits++;
  75.         }
  76.         return digits;
  77.     }
  78.     //根据几位数返回char
  79.     private static char getCharByDigits(int digit, String string){
  80.         char[] charArray = string.toCharArray();
  81.         return charArray[digit - 1];
  82.     }
  83.     private static int getSize(char letter){
  84.         if('a' <= letter && letter <= 'z'){
  85.             //a返回的是0
  86.             return letter - 97;
  87.         } else if ('A' <= letter && letter <= 'Z') {
  88.             //A返回的是26
  89.             return letter - 65 + 26;
  90.         }
  91.         return -1;
  92.     }
  93.     public static void main(String[] args) {
  94.         String inputFilePath1 = "D:\\develop\\projects\\dataStructure\\homework02\\src\\Homework04\\radixSort1.txt";
  95.         String outputFilePath1 = "D:\\develop\\projects\\dataStructure\\homework02\\src\\Homework04\\sorted_numbers.txt";
  96.         String inputFilePath2 = "D:\\develop\\projects\\dataStructure\\homework02\\src\\Homework04\\radixSort2.txt";
  97.         String outputFilePath2 = "D:\\develop\\projects\\dataStructure\\homework02\\src\\Homework04\\sorted_letters.txt";
  98.         int[] numbers = readNumbersFromFile(inputFilePath1);
  99.         String[] letters = readStringsFromFile(inputFilePath2);
  100.         // 打印原始数组
  101.         System.out.println("原始数组:");
  102.         for (int number : numbers) {
  103.             System.out.print(number + " ");
  104.         }
  105.         System.out.println();
  106.         // 进行基数排序
  107.         long startTime = System.nanoTime(); // 记录开始时间
  108.         LSD(numbers);
  109.         long endTime = System.nanoTime(); // 记录结束时间
  110.         // 打印排序后的数组
  111.         System.out.println("排序后的数组:");
  112.         for (int number : numbers) {
  113.             System.out.print(number + " ");
  114.         }
  115.         System.out.println();
  116.         // 计算并打印执行时间
  117.         double duration = (double) (endTime - startTime) / 1000000;
  118.         System.out.println("LSD(numbers)的执行时间为: " + duration + "毫秒");
  119.         // 将排序结果输出到文件
  120.         writeNumbersToFile(numbers, outputFilePath1);
  121.         System.out.println("排序结果已写入文件: " + outputFilePath1);
  122.         // 打印原始字母数组
  123.         System.out.println("原始字母数组:");
  124.         for (String letter : letters) {
  125.             System.out.print(letter + " ");
  126.         }
  127.         System.out.println();
  128.         // 进行基数排序
  129.         startTime = System.nanoTime(); // 记录开始时间
  130.         LSD(letters);
  131.         endTime = System.nanoTime(); // 记录结束时间
  132.         // 打印排序后的字母数组
  133.         System.out.println("排序后的字母数组:");
  134.         for (String letter : letters) {
  135.             System.out.print(letter + " ");
  136.         }
  137.         System.out.println();
  138.         // 计算并打印执行时间
  139.         duration = (double) (endTime - startTime) / 1000000;
  140.         System.out.println("LSD(letters)的执行时间为: " + duration + "毫秒");
  141.         // 将排序结果输出到文件
  142.         writeStringsToFile(letters, outputFilePath2);
  143.         System.out.println("排序结果已写入文件: " + outputFilePath2);
  144.     }
  145.     private static int[] readNumbersFromFile(String filePath) {
  146.         int[] numbers = null;
  147.         try {
  148.             File file = new File(filePath);
  149.             Scanner scanner = new Scanner(file);
  150.             //计算文件中有多少个数
  151.             int count = 0;
  152.             while (scanner.hasNext()) {
  153.                 if (scanner.hasNextInt()) {
  154.                     scanner.nextInt();
  155.                     count++;
  156.                 } else {
  157.                     scanner.next();
  158.                 }
  159.             }
  160.             scanner.close();
  161.             //把数字填入数组
  162.             numbers = new int[count];
  163.             scanner = new Scanner(file);
  164.             int index = 0;
  165.             while (scanner.hasNext()) {
  166.                 if (scanner.hasNextInt()) {
  167.                     numbers[index] = scanner.nextInt();
  168.                     index++;
  169.                 } else {
  170.                     scanner.next();
  171.                 }
  172.             }
  173.             scanner.close();
  174.         } catch (FileNotFoundException e) {
  175.             e.printStackTrace();
  176.         }
  177.         return numbers;
  178.     }
  179.     private static String[] readStringsFromFile(String filePath) {
  180.         String[] strings = null;
  181.         try {
  182.             File file = new File(filePath);
  183.             Scanner scanner = new Scanner(file);
  184.             // 计算文件中有多少个字符串
  185.             int count = 0;
  186.             while (scanner.hasNext()) {
  187.                 if (scanner.hasNext()) {
  188.                     scanner.next();
  189.                     count++;
  190.                 } else {
  191.                     scanner.next();
  192.                 }
  193.             }
  194.             scanner.close();
  195.             // 把字符串填入数组
  196.             strings = new String[count];
  197.             scanner = new Scanner(file);
  198.             int index = 0;
  199.             while (scanner.hasNext()) {
  200.                 if (scanner.hasNext()) {
  201.                     strings[index] = scanner.next();
  202.                     index++;
  203.                 } else {
  204.                     scanner.next();
  205.                 }
  206.             }
  207.             scanner.close();
  208.         } catch (FileNotFoundException e) {
  209.             e.printStackTrace();
  210.         }
  211.         return strings;
  212.     }
  213.     private static void writeNumbersToFile(int[] numbers, String filePath) {
  214.         try {
  215.             PrintWriter writer = new PrintWriter(filePath);
  216.             int count = 0;
  217.             for (int number : numbers) {
  218.                 writer.print(number + " ");
  219.                 count++;
  220.                 if (count % 50 == 0) {
  221.                     writer.println();
  222.                 }
  223.             }
  224.             writer.close();
  225.         } catch (FileNotFoundException e) {
  226.             e.printStackTrace();
  227.         }
  228.     }
  229.     private static void writeStringsToFile(String[] strings, String filePath) {
  230.         try {
  231.             PrintWriter writer = new PrintWriter(filePath);
  232.             int count = 0;
  233.             for (String str : strings) {
  234.                 writer.print(str + " ");
  235.                 count++;
  236.                 if (count % 50 == 0) {
  237.                     writer.println();
  238.                 }
  239.             }
  240.             writer.close();
  241.         } catch (FileNotFoundException e) {
  242.             e.printStackTrace();
  243.         }
  244.     }
  245. }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值