目录

数组的排序

Algorithm Notes

Mark Chen | 09 Mar 2021

你使用哪种语言?我们会根据你选择的语言显示对应的代码示例

Python
Java

注意,这个页面并不是描述排序算法的页面。这个页面描述的是各个语言具体如何实现对数组的排序。如果你想看排序算法相关的内容,点击这里

前置条件

在 Java 中,一般我们只会对 ArrayList 中的对象进行排序操作。在对数组排序时,我们会使用 ArrayList.sort 方法。这个方法不会有返回值,会直接在原来的 ArrayList 上进行修改。

在 Java 中对语言自带的类型进行排序

如果我们要对数组进行排序,仅仅调用 ArrayList.sort() 是不够的,我们还要告诉 Java 如何对 List 中的对象进行排序 - 这里我们就要引入 Comparator 的概念了。Comparator 对象是专门用来比较两个对象大小的。ArrayList 进行排序的时候会调用传入的 Comparator 对象进行排序。


import java.util.*;

public class compareExample {
    public static void main(String[] args) {
        ArrayList<Integer> arr = new ArrayList<>();
        arr.add(1);
        arr.add(8);
        arr.add(3);
        arr.add(5);
        arr.sort(Comparator.naturalOrder());
        System.out.println(arr);    // Output: [1, 3, 5, 8]
        arr.sort(Comparator.reverseOrder());
        System.out.println(arr);    // Output: [8, 5, 3, 1]
    }
}

在上面这段代码中,我们向 arr 传入了 Comparator.naturalOrder() 这样一个对象。如同它的名字所描述的,这个比较器的作用就是将Array中的内容按照“自然顺序”排列 - 也就是从小到大的顺序。类似naturalOrderjava.util.Comparator 中还有其他的比较器,例如 reverseOrder (将List中的元素从大到小的排序)等。

如果你想排序的 ArrayList 中存放的元素是 Java 已给出的数据类型,那么对它们进行排序就只需要上面这一步就好了。然而,如果你想对自己声明的类型进行排序,我们还需要做一些额外的工作……

在 Java 中对自己定义的类进行排序

如果你想对自己定义的类进行排序,那么有以下两种方法实现:

  1. 在自己的类中实现 Java 定义的 Comparable 接口
  2. 自己为自己的类实现一个 Comparator 类型,并将这个 Comparator 传到 arr.sort() 中。

下面,假设我们要对这样一个 Java 类进行排序:


class Person{
    public int age;
    public String name;

    public Person(int age, String name){
        this.age = age;
        this.name = name;
    }

    public String toString(){
        return "( age: " + this.age + ", name: " + this.name + " )";
    }
}

实现 Comparable 接口 (推荐)

通过实现 Comparable 接口,Java会识别到这个类是“可比较的”。在调用 ArrayList.sort()的时候,Comparator会调用你的类中的 compareTo方法来判断两个对象之间的大小关系。


public int compareTo(object other){
    /*
        If this object is bigger than the "other" given in the parameter, return a positive integer.
        if this object is smaller than the "other" given in parameter, return a negative integer.
        Otherwise, return 0.
    */
}
具体的写法如下:

class Person implements Comparable<Person>{
    public int age;
    public String name;

    public Person(int age, String name){
        this.age = age;
        this.name = name;
    }

    public int compareTo(Person other){
        if (this.equals(other)){ return 0; }
        if (this.age != other.age){ return this.age - other.age; }
        else{ return this.name.compareTo(other.name); }
    }

    public boolean equals(Person other){
        return this.age == other.age && this.name.equals(other.name);
    }

    public String toString(){
        return "( age: " + this.age + ", name: " + this.name + " )";
    }
}

如果你在自己的Java类上实现了Comparable 接口,那么在数组排序时所有 java.util.Comparator 中的比较器都可以直接传入给 ArrayList


import java.util.*;

public class compareTest{
    public static void main(String[] args) {
        ArrayList<Person> arr = new ArrayList<>();
        arr.add(new Person(17, "Mark"));
        arr.add(new Person(14, "Test"));
        arr.add(new Person(18, "wyn"));
        arr.sort(Comparator.naturalOrder());
        System.out.println(arr);
    }
}
运行结果:
[( age: 14, name: Test ), ( age: 17, name: Mark ), ( age: 18, name: wyn )]

实现 Comparator 类型 (不推荐)

除了直接实现Comparable接口以外,我们还可以直接定义一个可以比较两个自己类型对象的比较器(Comparator)来达到对存储这个类型对象的数组进行排序的目的。这种情况下,我们要新建一个比较器类型来实现 Comparator 抽象类。Comparator 抽象类只有一个函数 - compare。所以如果我们想实现一个效果和上文的方案相同的比较器,我们可以这样写:


class PersonComparator implements Comparator<Person>{
    public int compare(Person o1, Person o2){
        if (o1 == o2){ return 0; }
        if (o1.age != o2.age) { return o1.age - o2.age; }
        else{ return o1.name.compareTo(o2.name); }
    }
}

这种情况下,我们要对Person类进行排序就需要将自己定义的比较器传入ArrayList.sort()中来进行排序。


import java.util.*;

public class compareTest{
    public static void main(String[] args) {
        ArrayList<Person> arr = new ArrayList<>();
        arr.add(new Person(17, "Mark"));
        arr.add(new Person(14, "Test"));
        arr.add(new Person(18, "wyn"));
        arr.sort(new PersonComparator());
        System.out.println(arr);
    }
}

输出结果如下:

[( age: 14, name: Test ), ( age: 17, name: Mark ), ( age: 18, name: wyn )]

Python 对元组和内置数据类型的排序

对于 Python 来说,数组的排序会变得简单很多:在Python中,你可以随意创建含有多个不同类型元素的数据对象 - Tuple (元组)。Python在比较元组大小时会先比较第0位大小,如果大小相同比较第1位……依此类推,直到找到最后一位或者大小不同的一位为止。

例子:


arr = [(1, "test"), (10, "ebc"), (10, "abc"), (3, "wasd")]
arr.sort()

结果:

[(1, 'test'), (3, 'wasd'), (10, 'abc'), (10, 'ebc')]

Python 对自己写的类进行排序

如果你试着直接对一个装有自己写的类的对象进行排序,Python会丢出这样的报错:

Traceback (most recent call last):
  File "d:\Python_Files\prime\compareV1.py", line 8, in <module>
    ...
TypeError: '>' not supported between instances of 'Person' and 'Person'

这是因为我们没有规定如何比较两个 Person 对象之间的大小关系。和Java类似,在Python中也有两种方法对自己写的类进行排序。

  1. 为自己的类实现 __gt__, __lt__, __eq__ 等方法使类的对象之间支持比较大小
  2. 在 arr.sort 时手动写入一个Comparator函数用来比较两个对象的大小关系,以 keyword arguement 形式将这个函数传入sort方法。

假设我们要对装有这样一个类的对象的数组进行排序……


class Person:
    def __init__(self, age, name):
        self.age = age
        self.name = name
        
    def __repr__(self):
        return "(age: {}, name: {})".format(self.age, self.name)

实现Python内置的,用于比较大小的方法

这种方法和 Java 中为自己的类实现 Comparable 接口非常相似,唯一的不同是没有显式的表明自己实现了Comparable接口/功能。 Python 中一共有五个内置的,用于比较对象之间大小关系的函数:__gt__, __lt__, __eq__, __ge__, __le__,分别对应大于,小于,相等,大于等于 和 小于等于 的判断。这里我们只需要实现前三个函数就好了。这三个函数返回一个布尔值,表明自己和传入的对象之间的关系是否是函数名所描述的关系。


class Person:
    def __init__(self, age, name):
        self.age = age
        self.name = 
    
    def __repr__(self):
        return "(age: {}, name: {})".format(self.age, self.name)
    
    def __lt__(self, otherPerson):
        if self.age != otherPerson.age: return self.age < otherPerson.age
        else: return self.name < otherPerson.name
    
    def __gt__(self, otherPerson):
        if self.age != otherPerson.age: return self.age > otherPerson.age
        else: return self.name > otherPerson.name
    
    def __eq__(self, otherPerson):
        return self.age == otherPerson.age and self.name == otherPerson.name

效果:


arr = [Person(23, "test1"), Person(17, "John"), Person(19, "Penny"), Person(21, "Mary"), Person(23, "test2")]
arr.sort()
print(arr)
[(age: 17, name: John), (age: 19, name: Penny), (age: 21, name: Mary), (age: 23, name: test1), (age: 23, name: test2)]

List.sort()传入比较方法

在我们调用sort方法时,有一个隐藏的 keyword arguement 是 key = function。这里的 function 是一个函数。如果这个参数被传递进了 sort 方法,那么在sort在比较大小时,Python会先将list中的对象传入 function,然后根据函数返回值进行排序。例如我们只想通过 Person 的 age 属性对 Person 对象进行排序,那么我们可以用这样一个 lambda 函数作为 key 传入 sort 方法。(注意:这时候即使 Person 没有实现 __lt__, __gt__, __eq__ 方法也可以进行排序。)


arr = [Person(23, "test1"), Person(17, "John"), Person(19, "Penny"), Person(21, "Mary"), Person(23, "test2")]
arr.sort(key= lambda personObj: personObj.age)
print(arr)

当然,如果 key 所对应的函数逻辑较为复杂,也可以在外面定义完以后再传入key参数。


def convertPerson(personObj):
    # convert a person object into a comparable object
    return str(personObj.age) + personObj.name

arr = [Person(23, "test1"), Person(17, "John"), Person(19, "Penny"), Person(21, "Mary"), Person(23, "test2")]
arr.sort(key = convertPerson)
print(arr)

练习


评论区