拼图游戏中逆序数的实现的三种方式

一、定义

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。如2431中,21,43,41,31是逆序,逆序数是4。

二、计算方法

逆序数的计算方法主要有直接计算归并树状数组三种,下面将一一介绍。

2.1 直接计算

即根据定义用枚举的方法求逆序数。对于数列中的每一个数a[i],遍历数列中的数a[j] (其中j < i),若a[i] < a[j],则逆序数加1,这样就能统计出该数列的逆序数总和。

该方法的时间复杂度为O(n^2)

代码实现如下:

public static void main(String[] args) {
        int[] array = {1,3,5,4,7, 8, 2, 6, 9};
        int arraySize = array.length;
        int inverseNumber = 0;
        for (int i = 0; i < arraySize; i++) {
            for (int j = i + 1; j < arraySize; j++) {
                if (array[i] > array[j])
                    inverseNumber++;
            }
    }
        System.out.print("数列:");
        for(int i=0;i<arraySize;i++)
            System.out.print(array[i]+" ");
        System.out.println("\n数列的逆序数:"+inverseNumber);
    }

结果为:

数列:1 3 5 4 7 8 2 6 9
数列的逆序数:8

2.2 采用归并思想

参考求逆序数。 
归并排序是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的,然后再把有序的子序列合并为整体有序序列归并排序是分治算法的一个典型的应用,而且是稳定的一种排序。这题利用归并排序的过程中,计算每个小区间的逆序数,进而得到大区间的逆序数。那么,问题就解决了。

假设以mid为区分,第start到第mid个元素属于左区间,第mid+1到第end个元素属于右区间。由于每个小区间都是良序的,当左区间的数字a[i]大于右区间的数字a[j]时,可以保证左区间第i到第mid这mid-i+1个元素都大于a[j],即元素a[j]在逆序数计算中被计入mid-i+1次。 
排序完成后,累加的结果即所有元素对逆序数的贡献和。

该方法的时间复杂度与归并排序的时间复杂度相同为O(nlogn)。

代码实现如下:

public class InverseNumberByMerge {
    private int method = 0;
    private int inverseNumber = 0;

    void Merge(int[] array, int start, int mid, int end) // 归并排序的合并部分
    {
        int[] b = new int[array.length];
        int i = start, j = mid + 1, k = start;
        while (i <= mid && j <= end) {
            if (array[i] <= array[j]) {
                b[k++] = array[i++];
            } else {
                inverseNumber += j - k;// 等价于mid-i+1,因为左右两区间都为排序后的结果,array[i]>array[j]意味着array中从i到mid的元素都大于array[j]
                b[k++] = array[j++];
            }
        }
        while (i <= mid) {
            b[k++] = array[i++];
        }
        while (j <= end) {
            b[k++] = array[j++];
        }
        for (i = start; i <= end; i++) {
            array[i] = b[i];
        }
    }

    private void MergeSort(int[] array, int start, int end) // 归并排序
    {
        if (start < end) {
            int mid = (start + end) / 2;
            MergeSort(array, start, mid); // 将前半部分排序
            MergeSort(array, mid + 1, end); // 将后半部分排序
            Merge(array, start, mid, end); // 合并前后两个部分
        }
    }
    public int getInverseNumber(){
        return this.inverseNumber;
    }
    public static void main(String[] args) {
        //数列:3 5 4 8 2 6 9
                // Arrange
                int[] array = {3,5,4,8,2,6,9};
                int arraySize = array.length;
                int inverseNumber = 0;
                InverseNumberByMerge comp = new InverseNumberByMerge();
                comp.MergeSort(array, 0, arraySize - 1);
                inverseNumber = comp.getInverseNumber();
                System.out.print("数列:");
                for(int i=0;i<arraySize;i++)
                    System.out.print(array[i]+" ");
                System.out.println("\n数列的逆序数:"+inverseNumber);
    }
}

结果为:

数列:2 3 4 5 6 8 9
数列的逆序数:6

2.3 树状数组

树状数组的介绍:树状数组

求逆序数的思路,参考并修改自逆序数的几种求法

以数列 3 5 4 8 2 6 9 为例,大体思路如下:

  1. 新建一个数组,将数组中每个元素置0 
    0 0 0 0 0 0 0
  2. 取数列中最大的元素,将该元素所在位置置1 
    0 0 0 0 0 0 1 
    统计该位置前已放置的元素的个数,为0
  3. 接着取第二大元素8,将第其位置置1 
    0 0 0 1 0 0 1 
    统计该位置前已放置的元素的个数,为0
  4. 继续取第三大元素6,将其位置置1 
    0 0 0 1 0 1 1 
    统计该位置前已放置的元素的个数,为1
  5. ……..

这样直到取出最小元素,将每次取元素时该元素前已放置的元素的个数进行累加,这样就算出总的逆序数来了。

可以很明显的看出,每次取新的元素a[i]时,该元素的位置i前已放置的元素个数n代表a[i]前有n个元素比a[i]大,即有n对逆序对,逆序对的第二个元素为a[i]。

但是,如果在统计和计算每次放某个元素时,一个一个地数该元素前边已放置元素的个数,那么一趟复杂度为O(n),总共操作n趟,复杂度为O(n^2),和第一种方法的复杂度一样了。所以,我们采用树状数组,把每一趟计数个数的复杂度降为O(logn),这样整个复杂度就变为O(nlogn)。

将序列中的每个数按照从大到小的顺序插入到树状数组中,给当前插入节点及其父节点的个数加1,然后统计该节点下边及左边放置元素的个数。这与前面讲述的大体思路中的过程是一致的。

逆序数的几种求法中,写的是该节点下边及右边,但这与思路中的说法不同,经过分析代码,每一次求和时,pos-=LowBit(pos);这正是去求该节点的左节点的公式,所以应为该节点下边及左边放置元素的个数

代码如下:

public class Node {
    public int data;
    public int pos;
    public Node(){

    }
    public Node(int data,int pos){
        this.data = data;
        this.pos = pos;
    }
    @Override
    public String toString() {

        return "data = "+this.data +"\npos = "+this.pos;
    }
}
----------------------------------------------------
import java.util.Comparator;
public class NodeComparator implements Comparator {
    public int compare(Object num1, Object num2) {
        return( (Node)num2).data-((Node)num1).data;
    }
}
----------------------------------------------------
import java.util.Arrays;
public class InverseNumberByTreemap {
    private int Length = 0;
    private int[] TreeArray = null;
    public InverseNumberByTreemap(int[] array) {
        this.TreeArray = array;
        this.Length = array.length;
    }

    private int LowBit(int num) {
        //System.out.println(num & (num ^ (num - 1)));
        return num & (num ^ (num - 1));
    }

    // 统计pos节点下方及左边节点元素的个数
    private int sum(int[] TreeArray, int pos) {
        int result = 0;
        while (pos != 0) {
            result += TreeArray[pos-1];
            pos -= LowBit(pos);
        }
        return result;
    }

    // 使pos位置的节点及其父节点的元素个数加1
    private void INC(int[] TreeArray, int pos) {
        while (pos < Length) {
            TreeArray[pos-1]++;
            pos += LowBit(pos);
        }
    }

    int insertNum(Node[] seq) {
        int reverseNum = 0;
        for (int i = 0; i < Length; i++) {
            reverseNum += sum(TreeArray, seq[i].pos); // 累加逆序数
            INC(TreeArray, seq[i].pos); // 将该节点及父节点的数加1
        }
        return reverseNum;
    }

    public static void main(String[] args) {

         int array[]={3,5,4,8,2,6,9};
        int Length = array.length;
        Node[] seq = new Node[Length];
        seq[0] = new Node(0, 0);
        for (int i = 0; i < seq.length; i++) {
            seq[i] = new Node(array[i], i+1);
//          System.out.println(seq[i]);
        }
        int[] TreeArray = new int[Length];
        for(int i=0;i<TreeArray.length;i++)
            TreeArray[i] = 0;
        // 从大到小排序
        NodeComparator comp = new NodeComparator();
        Arrays.sort(seq, comp);
        int reverseNum = 0;
        // 边插入边计数
        InverseNumberByTreemap inverse = new InverseNumberByTreemap(TreeArray);
        reverseNum = inverse.insertNum(seq);
        System.out.println(reverseNum);
    }
}

结果为:

数列:2 3 4 5 6 8 9
数列的逆序数:6
时间: 2024-10-25 08:13:22

拼图游戏中逆序数的实现的三种方式的相关文章

Velocity中加载vm文件的三种方式

Velocity中加载vm文件的三种方式: a.  加载classpath目录下的vm文件 Properties p = new Properties(); // 加载classpath目录下的vm文件 // 这里是加载模板VM文件,比如:/META-INF/template/Web_B2CPayment.vm(请参考mas_spring_integration.xml) p.setProperty("file.resource.loader.class", "org.apa

转 Velocity中加载vm文件的三种方式

Velocity中加载vm文件的三种方式 velocitypropertiespath Velocity中加载vm文件的三种方式:   方式一:加载classpath目录下的vm文件 Properties p = new Properties(); p.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); Ve

iOS中的视图跳转的三种方式(代码跳转,根据桥跳转,按钮跳转)

#import "ViewController.h" #import "SecondViewController.h" @interface ViewController () @property (retain, nonatomic) IBOutlet UITextField *textField; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // D

Web开发中获取Spring的ApplicationContext的三种方式

在 WEB 开发中,可能会很少需要显示的获得 ApplicationContext 来得到由 Spring 进行管理的某些 Bean, 今天我就遇到了,在这里和大家分享一下, WEB 开发中,怎么获取 ApplicationContext 一       要想怎么获取 ApplicationContext, 首先必须明白 Spring 内部 ApplicationContext 是怎样存储的.下面我们来跟踪一下源码 首先:从大家最熟悉的地方开始 Java代码   <listener> <

github项目解析(八)--&gt;Activity启动过程中获取组件宽高的三种方式

转载请标明出处:一片枫叶的专栏 上一个github小项目中我们介绍了防止按钮重复点击的小框架,其实现的核心逻辑是重写OnClickListener的onClick方法,添加防止重复点击的逻辑,即为第二次点击与第一次点击的时间间隔添加阙值,若第二次点击的时间间隔与第一次点击的时间间隔小于阙值,则此次点击无效,再次基础上我们又封装了点击组件验证网络Listener,点击组件验证是否登录Listener等,具体可参考:github项目解析(七)–>防止按钮重复点击 本文中我将介绍一下android中A

javaweb中加载外部文件的三种方式

1.request.getSession().getServletContext().getResourceAsStream("/WEB-INF/classes/a.txt""); / 相对对于项目的根路径 2.getClass().getClassLoader().getResourceAsStream("/a.txt") / 不管有没有'/' 都相对于classpath路径(/WEB-INF/classes/) 3.getClass().getReso

拼图小游戏的逆序数

逆序数 首先前言:逆序数和拼图有啥关系呢,逆序数是啥 在拼图中,逆序数为偶数,才能拼图成功,奇数是不能成功的!! 逆序数:通过百度查询,得知:一个排序中所有逆序的总数和叫做逆序数; 比如: 4132 > 这是一个4小块的拼图 它这里的倒序有 41,43,42,32 所以这里的逆序数就是4(偶数); 所以,我们只需要关注逆序数就可以,它的前提就是 前面大于后面! let arr3 = []; let a = 0; for(let i = 0;i<this.len;i++){ arr3.push(

[原创]ActionScript3游戏中的图像编程(连载三十二)

2.2.5 投影距离的模拟 Photoshop投影样式面板的下一个属性是距离,它也存在于Flash的投影滤镜选项中.两者初始值一致,经笔者测试,两者在效果实现和数值意义方面基本一致.Flash不需要对默认参数进行更改. 下一项是扩展,乍一看,在Flash中并没有找到对应项.但仔细观察,在Photoshop投影样式的基础选项里,除了alpha以外,就只剩该属性用了百分比. [原创]ActionScript3游戏中的图像编程(连载三十二),布布扣,bubuko.com

[原创]ActionScript3游戏中的图像编程(连载三十)

2.2.3 Photoshop/Flash中的投影品质 与Photoshop不同,Flash的滤镜在输出的作品中仍会实时通过FlashPlayer进行渲染,所以性能显得尤为重要,在迫不得已的情况下还要以牺牲品质作为代价.所以,Flash的品质下拉框引起了我的注意,我试着把品质调整为“高”,效果就可以跟Photoshop的媲美了.(图 2.18) 对于品质,Flash的帮助文件也给出了解释,品质的高低差别在FlashPlayer内部是通过对低品质滤镜的使用次数不同来进行控制的,低品质只模糊1次,高